├── .gitignore
├── LICENSE
├── README.md
├── cpr
├── __init__.py
├── cones.py
├── cvxpy_interface.py
├── lsqr.py
├── problem.py
├── refine.py
├── solvers.py
├── sparse_matvec.py
└── utils.py
├── examples
├── comparison.png
├── experiments.ipynb
└── improvement.png
├── pip_upload.sh
├── setup.py
└── test
├── test_cones.py
└── test_problem.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.DS_Store
2 | *.egg
3 | *.pyc
4 | *.egg-info*
5 | *__pycache__*
6 |
7 | # Build
8 | build/*
9 | dist/*
10 |
11 | # Notebooks
12 | *.ipynb_checkpoints*
13 |
14 | # Examples and local experiments
15 | *.ipynb
16 | *.png
17 |
18 | # Profiling
19 | *.dat
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # `cpr`: Cone program refinement
2 |
3 | `cpr` is a Python library
4 | for the iterative refinement
5 | of a primal-dual solution
6 | (or a certificate of unboundedness or infeasibility)
7 | of a cone program.
8 |
9 | ### Refinement
10 |
11 | Given an approximate solution (or certificate),
12 | meaning one for which the optimality
13 | conditions don't hold exactly,
14 | `cpr` produces a new approximate solution for which
15 | the norm of the violations of the primal and dual systems,
16 | and the duality gap, is smaller.
17 |
18 |
19 | **Mathematics.**
20 | It does so by differentiating
21 | the operator 𝒩 (z) ∈ 𝗥^(n),
22 | the concatenation of the violations of the
23 | primal and dual systems of the problem, and the duality gap,
24 | for any approximate primal-dual solution
25 | (or certificate) represented by z ∈ 𝗥^(n),
26 | via an [embedding](https://www.jstor.org/stable/3690376)
27 | of the conic [optimality conditions](https://arxiv.org/pdf/1312.3039.pdf).
28 | See the [accompanying paper](http://stanford.edu/~boyd/papers/pdf/cone_prog_refine.pdf) for more details.
29 | So, 𝒩 (z) = 0 if and only if z is an exact primal-dual solution
30 | or certificate.
31 | `cpr` proceeds iteratively, using at each steps the value of 𝒩 (z)
32 | and the derivative matrix 𝗗𝒩 (z) to approximately solve
33 | the linear system that locally approximates the conic optimality conditions.
34 |
35 | **Matrix-free.**
36 | `cpr` is matrix-free, meaning that it does not store or
37 | invert the derivative matrix 𝗗𝒩 (z).
38 | In other words, `cpr` only uses O(n) memory space
39 | in addition to the problem data,
40 | where n is the size of a primal-dual solution.
41 |
42 | **Iterative solution.**
43 | `cpr` uses [LSQR](http://web.stanford.edu/group/SOL/software/lsqr/),
44 | an iterative linear system solver, to approximately solve a system
45 | that locally approximates the conic optimality conditions.
46 | The number of LSQR iterations is chosen by the user (by default, for small problems, 30),
47 | as is the number of `cpr` iterations (by default, for small problems, 2).
48 |
49 | **Problem classes.**
50 | `cpr` can currently solve cone programs whose cone constraints are products of
51 | the [zero cone](https://en.wikipedia.org/wiki/System_of_linear_equations),
52 | [the non-negative orhant](https://en.wikipedia.org/wiki/Linear_programming),
53 | and any number of [second-order cones](https://en.wikipedia.org/wiki/Second-order_cone_programming),
54 | [exponential cones](https://yalmip.github.io/tutorial/exponentialcone/),
55 | and [semidefinite cones](https://en.wikipedia.org/wiki/Semidefinite_programming).
56 |
57 | **Paper.**
58 | A much more detailed description of the algorithm used is provided
59 | in the [accompanying paper](http://stanford.edu/~boyd/papers/pdf/cone_prog_refine.pdf).
60 |
61 | **Dependencies.**
62 | `cpr` depends on [`numpy`](http://www.numpy.org) for vector arithmetics,
63 | [`scipy`](https://www.scipy.org) for sparse linear algebra,
64 | and [`numba`](https://numba.pydata.org) for just-in-time code compilation.
65 | It currently runs on a single thread, on CPU.
66 |
67 | **Future plan.**
68 | The core library will be rewritten in ANSI C,
69 | and will
70 | support problems whose data is provided as
71 | an abstract linear operator.
72 |
73 |
74 | ### Installation
75 | To install, execute in a terminal:
76 |
77 | ```
78 | pip install cpr
79 | ```
80 |
81 |
82 | ### Minimal example (with `cvxpy`)
83 |
84 | `cpr` can be used in combination with
85 | [`cvxpy`](https://www.cvxpy.org),
86 | via the `cpr.cvxpy_solve` method.
87 |
88 | ```python
89 | import numpy as np
90 | import cvxpy as cp
91 | import cpr
92 |
93 | n = 5
94 | np.random.seed(1)
95 | X = cp.Variable((n,n))
96 |
97 | problem = cp.Problem(objective = cp.Minimize(cp.norm(X - np.random.randn(n, n))),
98 | constraints = [X @ np.random.randn(n) == np.random.randn(n)])
99 |
100 | cpr.cvxpy_solve(problem, presolve=True, verbose=False)
101 | print('norm of the constraint error, solving with SCS and then refining with CPSR: %.2e' %
102 | np.linalg.norm(problem.constraints[0].violation()))
103 |
104 | cpr.cvxpy_solve(problem, verbose=False)
105 | print('norm after refining with CPSR again: %.2e' %
106 | np.linalg.norm(problem.constraints[0].violation()))
107 | ```
108 |
109 | It has the following output. (Machine precision is around `1.11e-16`.)
110 |
111 | ```
112 | norm of the constraint error, solving with SCS and then refining with CPSR: 1.48e-11
113 | norm after refining with CPSR again: 5.21e-16
114 | ```
115 |
116 | ### Citing
117 |
118 | If you wish to cite `cpr`, please use the following BibTex:
119 |
120 | ```
121 | @article{cpr2019,
122 | author = {Busseti, E. and Moursi, W. and Boyd, S.},
123 | title = {Solution refinement at regular points of conic problems},
124 | journal = {Computational Optimization and Applications},
125 | year = {2019},
126 | volume = {74},
127 | number = {3},
128 | pages = {627--643},
129 | }
130 |
131 | @misc{cpr,
132 | author = {Busseti, E. and Moursi, W. and Boyd, S.},
133 | title = {{cpr}: cone program refinement, version 0.1},
134 | howpublished = {\url{https://github.com/cvxgrp/cone_prog_refine}},
135 | year = 2019
136 | }
137 | ```
138 |
139 |
--------------------------------------------------------------------------------
/cpr/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright 2019 Enzo Busseti, Walaa Moursi, and Stephen Boyd
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | """
16 |
17 | from .cones import *
18 | from .problem import *
19 | from .utils import *
20 | from .lsqr import lsqr
21 | from .solvers import *
22 | from .refine import *
23 | from .cvxpy_interface import *
24 |
--------------------------------------------------------------------------------
/cpr/cones.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright 2019 Enzo Busseti, Walaa Moursi, and Stephen Boyd
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 |
16 |
17 | ----------------------------------------------------
18 |
19 |
20 | Some parts (exponential cone projection) are adapted
21 | from Brendan O'Donoghue's scs-python.
22 | """
23 |
24 | import numpy as np
25 | import scipy.sparse as sp
26 | from collections import namedtuple
27 |
28 | import numba as nb
29 |
30 |
31 | class DimensionError(Exception):
32 | pass
33 |
34 |
35 | class NonDifferentiable(Exception):
36 | pass
37 |
38 |
39 | # def check_non_neg_int(n):
40 | # if not isinstance(n, int):
41 | # raise TypeError
42 | # if n < 0:
43 | # raise DimensionError
44 |
45 |
46 | # def check_right_size(x, n):
47 | # if (n > 1) and not (len(x) == n):
48 | # raise DimensionError
49 |
50 |
51 | cone = namedtuple('cone', ['Pi', 'D', 'DT'])
52 |
53 |
54 | @nb.jit(nb.float64[:](nb.float64[:], nb.float64[:]))
55 | def id_op(z, x):
56 | """Identity operator on x."""
57 | return np.copy(x)
58 |
59 |
60 | @nb.jit(nb.float64[:](nb.float64[:]))
61 | def free(z):
62 | """Projection on free cone, and cache."""
63 | return np.copy(z)
64 |
65 |
66 | free_cone = cone(free, id_op, id_op)
67 | free_cone_cached = cone(lambda z, cache: free_cone.Pi(z),
68 | lambda z, x, cache: free_cone.D(z, x),
69 | lambda z, x, cache: free_cone.D(z, x))
70 |
71 |
72 | @nb.jit(nb.float64[:](nb.float64[:], nb.float64[:]))
73 | def zero_D(z, x):
74 | """zero operator on x."""
75 | return np.zeros_like(x)
76 |
77 |
78 | @nb.jit(nb.float64[:](nb.float64[:]))
79 | def zero_Pi(z):
80 | """Projection on zero cone, and cache."""
81 | return np.zeros_like(z)
82 |
83 |
84 | zero_cone = cone(zero_Pi, zero_D, zero_D)
85 | zero_cone_cached = cone(lambda z, cache: zero_cone.Pi(z),
86 | lambda z, x, cache: zero_cone.D(z, x),
87 | lambda z, x, cache: zero_cone.D(z, x))
88 |
89 |
90 | @nb.jit(nb.float64[:](nb.float64[:], nb.float64[:]), nopython=True)
91 | def non_neg_D(z, x):
92 | # assert len(x) == len(z)
93 | result = np.copy(x)
94 | result[z < 0] = 0.
95 | return result
96 |
97 |
98 | @nb.jit(nb.float64[:](nb.float64[:]), nopython=True)
99 | def non_neg_Pi(z):
100 | """Projection on non-negative cone, and cache."""
101 | cache = np.zeros(1)
102 | return np.maximum(z, 0.)
103 |
104 |
105 | non_neg_cone = cone(non_neg_Pi, non_neg_D, non_neg_D)
106 | non_neg_cone_cached = cone(lambda z, cache: non_neg_cone.Pi(z),
107 | lambda z, x, cache: non_neg_cone.D(z, x),
108 | lambda z, x, cache: non_neg_cone.D(z, x))
109 |
110 |
111 | # @nb.jit(nb.float64[:](nb.float64[:], nb.float64[:], nb.float64[:]), nopython=True)
112 | # def sec_ord_D(z, x, cache):
113 | # """Derivative of projection on second order cone."""
114 | #
115 | # rho, s = z[0], z[1:]
116 | # norm = cache[0]
117 | #
118 | # if norm <= rho:
119 | # return np.copy(x)
120 | #
121 | # if norm <= -rho:
122 | # return np.zeros_like(x)
123 | #
124 | # y, t = x[1:], x[0]
125 | # normalized_s = s / norm
126 | # result = np.empty_like(x)
127 | # result[1:] = ((rho / norm + 1) * y -
128 | # (rho / norm**3) * s *
129 | # s@y +
130 | # t * normalized_s) / 2.
131 | # result[0] = (y@ normalized_s + t) / 2.
132 | # return result
133 |
134 | @nb.jit(nb.float64[:](nb.float64[:], nb.float64[:], nb.float64[:]))
135 | def sec_ord_D(z, dz, cache):
136 | """Derivative of projection on second order cone."""
137 |
138 | # point at which we derive
139 | t = z[0]
140 | x = z[1:]
141 | # projection of point
142 | s = cache[0]
143 | y = cache[1:]
144 |
145 | # logic for simple cases
146 | norm = np.linalg.norm(x)
147 | if norm <= t:
148 | return np.copy(dz)
149 | if norm <= -t:
150 | return np.zeros_like(dz)
151 |
152 | # big case
153 | dx, dt = dz[1:], dz[0]
154 |
155 | # from my notes (attach photo)
156 | alpha = 2 * s - t
157 | if alpha == 0.:
158 | raise Exception('Second order cone derivative error')
159 | b = 2 * y - x
160 | c = dt * s + dx @ y
161 | d = dt * y + dx * s
162 |
163 | denom = (alpha - b @ b / alpha)
164 | if denom == 0.:
165 | raise Exception('Second order cone derivative error')
166 | ds = (c - b @ d / alpha) / denom
167 | dy = (d - b * ds) / alpha
168 |
169 | result = np.empty_like(dz)
170 | result[1:], result[0] = dy, ds
171 | return result
172 |
173 | # normalized_s = s / norm
174 | # result = np.empty_like(x)
175 | # result[1:] = ((rho / norm + 1) * y -
176 | # (rho / norm**3) * s *
177 | # s@y +
178 | # t * normalized_s) / 2.
179 | # result[0] = (y@ normalized_s + t) / 2.
180 | # return result
181 |
182 |
183 | @nb.jit(nb.float64[:](nb.float64[:], nb.float64[:]))
184 | def sec_ord_Pi(z, cache):
185 | """Projection on second-order cone."""
186 |
187 | x, rho = z[1:], z[0]
188 | # cache this?
189 | norm_x = np.linalg.norm(x)
190 | # cache = np.zeros(1)
191 | # cache[0] = norm_x
192 |
193 | if norm_x <= rho:
194 | return np.copy(z)
195 |
196 | if norm_x <= -rho:
197 | return np.zeros_like(z)
198 |
199 | result = np.empty_like(z)
200 |
201 | result[0] = 1.
202 | result[1:] = x / norm_x
203 | result *= (norm_x + rho) / 2.
204 |
205 | cache[:] = result
206 |
207 | return result
208 |
209 |
210 | sec_ord_cone = cone(sec_ord_Pi, sec_ord_D, sec_ord_D)
211 |
212 |
213 | # @njit
214 | # def _g_exp(z):
215 | # """Function used for exp. cone proj."""
216 | # r = z[0]
217 | # s = z[1]
218 | # t = z[2]
219 | # return s * np.exp(r / s) - t
220 |
221 |
222 | # @njit
223 | # def _gradient_g_exp(z):
224 | # """Gradient of function used for exp. cone proj."""
225 | # r = z[0]
226 | # s = z[1]
227 | # t = z[2]
228 | # result = np.empty(3)
229 | # result[0] = np.exp(r / s)
230 | # result[1] = result[0] * (1. - r / s)
231 | # result[2] = -1.
232 | # return result
233 |
234 |
235 | # @njit
236 | # def _hessian_g_exp(z):
237 | # """Hessian of function used for exp. cone proj."""
238 | # r = z[0]
239 | # s = z[1]
240 | # t = z[2]
241 | # result = np.zeros((3, 3))
242 | # ers = np.exp(r / s)
243 | # result[0, 0] = ers / s
244 | # result[0, 1] = - ers * r / s**2
245 | # result[1, 1] = ers * r**2 / s**3
246 | # result[1, 0] = result[0, 1]
247 | # return result
248 |
249 |
250 | # @njit
251 | # def _exp_residual(z_0, z, lambda_var):
252 | # """Residual of system for exp. cone projection."""
253 | # result = np.empty(4)
254 | # result[:3] = z - z_0 + lambda_var * _gradient_g_exp(z)
255 | # result[3] = _g_exp(z)
256 | # return result
257 |
258 |
259 | # @njit
260 | # def _exp_newt_matrix(z, lambda_var):
261 | # """Matrix used for exp. cone projection."""
262 | # result = np.empty((4, 4))
263 | # result[:3, :3] = lambda_var * _hessian_g_exp(z)
264 | # result[0, 0] += 1.
265 | # result[1, 1] += 1.
266 | # result[2, 2] += 1.
267 | # grad = _gradient_g_exp(z)
268 | # result[:3, 0] = grad
269 | # result[0, :3] = grad
270 | # result[3, 3] = 0.
271 | # return result
272 |
273 | # def project_exp_bisection(v):
274 | # v = copy(v)
275 | # r = v[0]
276 | # s = v[1]
277 | # t = v[2]
278 | # # v in cl(Kexp)
279 | # if (s * exp(r / s) <= t and s > 0) or (r <= 0 and s == 0 and t >= 0):
280 | # return v
281 | # # -v in Kexp^*
282 | # if (-r < 0 and r * exp(s / r) <= -exp(1) * t) or (-r == 0 and -s >= 0 and
283 | # -t >= 0):
284 | # return zeros(3,)
285 | # # special case with analytical solution
286 | # if r < 0 and s < 0:
287 | # v[1] = 0
288 | # v[2] = max(v[2], 0)
289 | # return v
290 |
291 | # x = copy(v)
292 | # ub, lb = get_rho_ub(v)
293 | # for iter in range(0, 100):
294 | # rho = (ub + lb) / 2
295 | # g, x = calc_grad(v, rho, x)
296 | # if g > 0:
297 | # lb = rho
298 | # else:
299 | # ub = rho
300 | # if ub - lb < 1e-6:
301 | # break
302 | # return x
303 |
304 |
305 | @nb.jit(nb.float64(nb.float64, nb.float64, nb.float64, nb.float64))
306 | def newton_exp_onz(rho, y_hat, z_hat, w):
307 | t = max(max(w - z_hat, -z_hat), 1e-6)
308 | for iter in np.arange(0, 100):
309 | f = (1 / rho**2) * t * (t + z_hat) - y_hat / rho + np.log(t / rho) + 1
310 | fp = (1 / rho**2) * (2 * t + z_hat) + 1 / t
311 |
312 | t = t - f / fp
313 | if t <= -z_hat:
314 | t = -z_hat
315 | break
316 | elif t <= 0:
317 | t = 0
318 | break
319 | elif np.abs(f) < 1e-6:
320 | break
321 | return t + z_hat
322 |
323 |
324 | @nb.jit(nb.float64[:](nb.float64[:], nb.float64, nb.float64))
325 | def solve_with_rho(v, rho, w):
326 | x = np.zeros(3)
327 | x[2] = newton_exp_onz(rho, v[1], v[2], w)
328 | x[1] = (1 / rho) * (x[2] - v[2]) * x[2]
329 | x[0] = v[0] - rho
330 | return x
331 |
332 |
333 | @nb.jit(nb.types.Tuple((nb.float64, nb.float64[:]))(nb.float64[:],
334 | nb.float64, nb.float64[:]),
335 | nopython=True)
336 | def calc_grad(v, rho, warm_start):
337 | x = solve_with_rho(v, rho, warm_start[1])
338 | if x[1] == 0:
339 | g = x[0]
340 | else:
341 | g = (x[0] + x[1] * np.log(x[1] / x[2]))
342 | return g, x
343 |
344 |
345 | @nb.jit(nb.types.UniTuple(nb.float64, 2)(nb.float64[:]))
346 | def get_rho_ub(v):
347 | lb = 0
348 | rho = 2**(-3)
349 | g, z = calc_grad(v, rho, v)
350 | while g > 0:
351 | lb = rho
352 | rho = rho * 2
353 | g, z = calc_grad(v, rho, z)
354 | ub = rho
355 | return ub, lb
356 |
357 |
358 | @nb.jit(nb.types.UniTuple(nb.float64[:], 2)(nb.float64[:]))
359 | def fourth_case_brendan(z):
360 | x = np.copy(z)
361 | ub, lb = get_rho_ub(x)
362 | # print('initial ub, lb', ub, lb)
363 | for i in range(0, 100):
364 | rho = (ub + lb) / 2
365 | g, x = calc_grad(z, rho, x)
366 | # print('g, x', g, x)
367 | if g > 0:
368 | lb = rho
369 | else:
370 | ub = rho
371 | # print('new ub, lb', ub, lb)
372 | if ub - lb < 1e-8:
373 | break
374 | # print('result:', x)
375 | return x, np.copy(x)
376 |
377 |
378 | # Here it is my work on exp proj D
379 | @nb.jit(nb.float64[:, :](nb.float64, nb.float64, nb.float64, nb.float64,
380 | nb.float64, nb.float64))
381 | def make_mat(r, s, t, x, y, z):
382 | mat = np.zeros((3, 3))
383 | # first eq.
384 | mat[0, 0] = 2 * x - r
385 | mat[0, 1] = 2 * y - s
386 | mat[0, 2] = 2 * z - t
387 | mat[1, 0] = np.exp(x / y)
388 | mat[1, 1] = np.exp(x / y) * (1 - x / y)
389 | mat[1, 2] = -1.
390 | mat[2, 0] = y
391 | mat[2, 1] = x - r
392 | mat[2, 2] = 2 * z - t
393 | return mat
394 |
395 | # Here it is my work on exp proj D
396 |
397 |
398 | @nb.jit(nb.float64[:, :](nb.float64, nb.float64, nb.float64, nb.float64,
399 | nb.float64, nb.float64))
400 | def make_mat_two(r, s, t, x, y, z):
401 | mat = np.zeros((3, 3))
402 | # first eq.
403 | u = -(r - x)
404 | v = -(s - y)
405 | w = -(t - z)
406 |
407 | mat[0, 0] = 2 * x - r
408 | mat[0, 1] = 2 * y - s
409 | mat[0, 2] = 2 * z - t
410 |
411 | # mat[1, 0] = np.exp(x / y)
412 | # mat[1, 1] = np.exp(x / y) * (1 - x / y)
413 | # mat[1, 2] = -1.
414 |
415 | mat[1, 0] = np.exp(v / u) * (1 - v / u)
416 | mat[1, 1] = np.exp(v / u)
417 | mat[1, 2] = np.e
418 |
419 | mat[2, 0] = y
420 | mat[2, 1] = x - r
421 | mat[2, 2] = 2 * z - t
422 | return mat
423 |
424 |
425 | @nb.jit(nb.float64[:](nb.float64, nb.float64, nb.float64, nb.float64,
426 | nb.float64, nb.float64))
427 | def make_error(r, s, t, x, y, z):
428 | error = np.zeros(3)
429 | error[0] = x * (x - r) + y * (y - s) + z * (z - t)
430 | error[1] = y * np.exp(x / y) - z
431 | error[2] = y * (x - r) + z * (z - t)
432 | # print('error', error)
433 | return error
434 |
435 |
436 | @nb.jit(nb.float64[:](nb.float64, nb.float64, nb.float64, nb.float64,
437 | nb.float64, nb.float64))
438 | def make_error_two(r, s, t, x, y, z):
439 | error = np.zeros(3)
440 | error[0] = x * (x - r) + y * (y - s) + z * (z - t)
441 | u = -(r - x)
442 | v = -(s - y)
443 | w = -(t - z)
444 | error[1] = np.e * w + u * np.exp(v / u)
445 | error[2] = y * (x - r) + z * (z - t)
446 | # print('error', error)
447 | return error
448 |
449 |
450 | @nb.jit(nb.float64[:](nb.float64, nb.float64, nb.float64, nb.float64,
451 | nb.float64, nb.float64))
452 | def make_rhs(x, y, z, dr, ds, dt):
453 | rhs = np.zeros(3)
454 | rhs[0] = x * dr + y * ds + z * dt
455 | rhs[2] = y * dr + z * dt
456 | return rhs
457 |
458 |
459 | @nb.jit(nb.float64[:](nb.float64, nb.float64, nb.float64, nb.float64,
460 | nb.float64, nb.float64, nb.float64, nb.float64, nb.float64))
461 | def fourth_case_D(r, s, t, x, y, z, dr, ds, dt):
462 |
463 | test = np.zeros(3)
464 | test[0], test[1], test[2] = r, s, t
465 |
466 | u = x - r
467 |
468 | if y < 1E-12:
469 | return np.zeros(3)
470 |
471 | # if y > -u and not y == 0.: # temporary fix?
472 | # print('computing error with e^(x/y)')
473 | error = make_error(r, s, t, x, y, z)
474 | # else:
475 | # print('computing error with e^(v/u)')
476 | # error = make_error_two(r, s, t, x, y, z)
477 |
478 | # error = make_error(r, s, t, x, y, z)
479 | rhs = make_rhs(x, y, z, dr, ds, dt)
480 | # print('base rhs', rhs)
481 | if np.any(np.isnan(error)) or np.any(np.isinf(error)) or np.any(np.isnan(rhs)):
482 | return np.zeros(3)
483 |
484 | # if y > -u and not y == 0.: # temporary fix?
485 | # print('solving system with e^(x/y)')
486 | result = np.linalg.solve(make_mat(r, s, t, x, y, z) + np.eye(3) * 1E-8,
487 | rhs - error)
488 | # else:
489 | # print('solving system with e^(v/u)')
490 | # result = np.linalg.solve(make_mat_two(r, s, t, x, y, z), # + np.eye(3) * 1E-8,
491 | # rhs - error)
492 |
493 | if np.any(np.isnan(result)):
494 | # raise Exception('Exp cone derivative error.')
495 | return np.zeros(3)
496 | # print('result', result)
497 | return result
498 |
499 |
500 | @nb.jit(nb.float64[:](nb.float64[:]))
501 | def fourth_case_enzo(z_var):
502 | # VERBOSE = False
503 |
504 | # print('fourth case Enzo')
505 | # if VERBOSE:
506 | # print('projecting (%f, %f, %f)' % (z_var[0], z_var[1], z_var[2]))
507 | real_result, _ = fourth_case_brendan(z_var)
508 | # print('brendan result: (x,y,z)', real_result)
509 | z_var = np.copy(z_var)
510 | r = z_var[0]
511 | s = z_var[1]
512 | t = z_var[2]
513 | # print('brendan result: (u,v,w)', real_result - z_var)
514 |
515 | x, y, z = real_result
516 | # if y < 1E-12:
517 | # y = 1E-12
518 |
519 | for i in range(10):
520 |
521 | # if y < 1e-14:
522 | # return np.zeros(3)
523 | u = x - r
524 | # assert y >= 0
525 | # assert u <= 0
526 | if (y <= 0. and u >= 0.):
527 | return np.zeros(3)
528 |
529 | if y > -u and not y == 0.:
530 | # print('computing error with e^(x/y)')
531 | error = make_error(r, s, t, x, y, z)
532 | else:
533 | # print('computing error with e^(v/u)')
534 | if u == 0:
535 | break
536 | error = make_error_two(r, s, t, x, y, z)
537 |
538 | # print('error:', error)
539 | # print('error norm: %.2e' % np.linalg.norm(error))
540 |
541 | # if VERBOSE:
542 | # print('iter %d, max |error| = %g' % (i, np.max(np.abs(error))))
543 |
544 | if np.max(np.abs(error)) < 1e-15:
545 | # print(np.max(np.abs(error)))
546 | # print('converged!')
547 | break
548 |
549 | if np.any(np.isnan(error)):
550 | break
551 |
552 | if y > -u and not y == 0.:
553 | # print('computing correction with e^(x/y)')
554 | correction = np.linalg.solve(
555 | make_mat(r, s, t, x, y, z) + np.eye(3) * 1E-8,
556 | -error)
557 | else:
558 | # print('computing correction with e^(v/u)')
559 | correction = np.linalg.solve(
560 | make_mat_two(r, s, t, x, y, z) + np.eye(3) * 1E-8,
561 | -error)
562 |
563 | # print('correction', correction)
564 |
565 | if np.any(np.isnan(correction)):
566 | break
567 |
568 | x += correction[0]
569 | y += correction[1]
570 | z += correction[2]
571 |
572 | result = np.empty(3)
573 | result[0] = x
574 | result[1] = y
575 | result[2] = z
576 |
577 | # if VERBOSE:
578 | # print('result = (%f, %f, %f)' % (result[0], result[1], result[2]))
579 |
580 | return result
581 |
582 |
583 | @nb.jit(nb.float64[:](nb.float64[:], nb.float64[:]), nopython=True)
584 | def exp_pri_Pi(z, cache):
585 | """Projection on exp. primal cone, and cache."""
586 | z = np.copy(z)
587 | r = z[0]
588 | s = z[1]
589 | t = z[2]
590 |
591 | # temporary...
592 | if np.linalg.norm(z) < 1E-14:
593 | cache[:3] = 0.
594 | return z
595 |
596 | # first case
597 | if (s > 0 and s * np.exp(r / s) <= t) or \
598 | (r <= 0 and s == 0 and t >= 0):
599 | cache[:3] = z
600 | return z
601 |
602 | # second case
603 | if (-r < 0 and r * np.exp(s / r) <= -np.exp(1) * t) or \
604 | (r == 0 and -s >= 0 and -t >= 0):
605 | cache[:3] = 0.
606 | return np.zeros(3)
607 |
608 | # third case
609 | if r < 0 and s < 0:
610 | z[1] = 0
611 | z[2] = max(z[2], 0)
612 | cache[:3] = z
613 | return z
614 |
615 | pi = fourth_case_enzo(z)
616 | cache[:3] = pi
617 | return pi
618 |
619 |
620 | @nb.jit(nb.float64[:](nb.float64[:], nb.float64[:], nb.float64[:]), nopython=True)
621 | def exp_pri_D(z_0, dz, cache):
622 | """Derivative of proj. on exp. primal cone."""
623 |
624 | r = z_0[0]
625 | s = z_0[1]
626 | t = z_0[2]
627 |
628 | dr = dz[0]
629 | ds = dz[1]
630 | dt = dz[2]
631 |
632 | # projection of z_0
633 | x = cache[0]
634 | y = cache[1]
635 | z = cache[2]
636 |
637 | # first case
638 | # if on the cone boundary, non-diff
639 | if (s > 0 and s * np.exp(r / s) == t) or \
640 | (r <= 0 and s == 0 and t >= 0): # or \
641 | # (r <= 0 and s == 0 and t == 0):
642 | # raise NonDifferentiable
643 | return np.zeros(3)
644 |
645 | if (s > 0 and s * np.exp(r / s) < t):
646 | # print('first case')
647 | return np.copy(dz)
648 |
649 | # second case
650 | # if on cone bound, then non-diff
651 | if (-r < 0 and r * np.exp(s / r) == -np.exp(1) * t) or \
652 | (r == 0 and -s >= 0 and -t >= 0): # or \
653 | # (r == 0 and -s >= 0 and -t == 0):
654 | # raise NonDifferentiable
655 | return np.zeros(3)
656 |
657 | if (-r < 0 and r * np.exp(s / r) < -np.exp(1) * t): # or \
658 | # (r == 0 and -s > 0 and -t > 0):
659 | # print('second case')
660 | return np.zeros(3)
661 |
662 | if r < 0 and s < 0 and t == 0:
663 | # raise NonDifferentiable
664 | return np.zeros(3)
665 |
666 | # third case
667 | if r < 0 and s < 0:
668 | # print('third case')
669 | result = np.zeros(3)
670 | result[0] = dz[0]
671 | result[2] = dz[2] if t > 0 else 0.
672 | # print('result', result)
673 | return result
674 |
675 | # fourth case
676 | fourth = fourth_case_D(r, s, t, x, y, z, dr, ds, dt)
677 | # assert not True in np.isnan(fourth)
678 | return fourth
679 |
680 |
681 | exp_pri_cone = cone(exp_pri_Pi, exp_pri_D, exp_pri_D)
682 |
683 |
684 | @nb.jit(nb.float64[:](nb.float64[:], nb.float64[:]))
685 | def exp_dua_Pi(z, cache):
686 | """Projection on exp. dual cone, and cache."""
687 | minuspi = exp_pri_Pi(-z, cache)
688 | return np.copy(z) + minuspi
689 |
690 |
691 | @nb.jit(nb.float64[:](nb.float64[:], nb.float64[:], nb.float64[:]))
692 | def exp_dua_D(z, x, cache):
693 | """Derivative of projection on exp. dual cone."""
694 | # res = exp_pri_D(z, x, cache)
695 | return np.copy(x) + exp_pri_D(-z, -x, cache)
696 |
697 |
698 | exp_dua_cone = cone(exp_dua_Pi, exp_dua_D, exp_dua_D)
699 |
700 |
701 | @nb.jit(nb.int64(nb.int64))
702 | def sizevec2sizemat(n):
703 | m = int(np.sqrt(2 * n))
704 | if not n == (m * (m + 1) / 2.):
705 | raise DimensionError
706 | return m
707 |
708 |
709 | @nb.jit(nb.int64(nb.int64))
710 | def sizemat2sizevec(m):
711 | return int((m * (m + 1) / 2.))
712 |
713 |
714 | @nb.jit(nb.float64[:](nb.float64[:, :]))
715 | def mat2vec(Z):
716 | """Upper tri row stacked, off diagonal multiplied by sqrt(2)."""
717 | n = Z.shape[0]
718 | l = sizemat2sizevec(n)
719 | result = np.empty(l)
720 | cur = 0
721 | sqrt_two = np.sqrt(2.)
722 |
723 | for i in range(n): # row
724 | result[cur] = Z[i, i]
725 | cur += 1
726 | for j in range(i + 1, n): # column
727 | result[cur] = Z[i, j] * sqrt_two
728 | cur += 1
729 |
730 | return result
731 |
732 |
733 | @nb.jit(nb.float64[:, :](nb.float64[:]))
734 | def vec2mat(z):
735 | n = sizevec2sizemat(len(z))
736 |
737 | cur = 0
738 | result = np.empty((n, n))
739 | sqrt_two = np.sqrt(2)
740 |
741 | for i in range(n): # row
742 | result[i, i] = z[cur]
743 | cur += 1
744 | for j in range(i + 1, n): # column
745 | result[i, j] = z[cur] / sqrt_two
746 | result[j, i] = result[i, j]
747 | cur += 1
748 |
749 | return result
750 |
751 |
752 | # @nb.jit() # nb.float64[:](nb.float64[:, :]))
753 | # def flatten(mat):
754 | # return mat.flatten()
755 |
756 |
757 | @nb.jit(nb.float64[:, :](nb.float64[:]), nopython=True)
758 | def reconstruct_sqmat(vec):
759 | n = int(np.sqrt(len(vec)))
760 | return np.copy(vec).reshape((n, n)) # copying because of numba issue
761 |
762 |
763 | @nb.jit((nb.int64, nb.float64[:], nb.float64[:], nb.float64[:, :],
764 | nb.float64[:, :]), nopython=True)
765 | def inner(m, lambda_plus, lambda_minus, dS_tilde, dZ_tilde):
766 | for i in range(m):
767 | for j in range(i, m):
768 | nom = lambda_plus[j]
769 | denom = lambda_plus[j] + lambda_minus[i]
770 | if (nom == 0.) and (denom == 0.):
771 | factor = 0.
772 | else:
773 | factor = nom / denom
774 | dS_tilde[i, j] = dZ_tilde[i, j] * factor
775 | dS_tilde[j, i] = dS_tilde[i, j]
776 |
777 |
778 | # nb.float64[:](nb.float64[:], nb.float64[:], nb.float64[:],
779 | @nb.jit(nb.float64[:](nb.float64[:],
780 | nb.float64[:],
781 | nb.float64[:],
782 | nb.float64[:]), nopython=True)
783 | def semidef_cone_D(z, dz, cache_eivec, cache_eival):
784 | U, lambda_var = reconstruct_sqmat(cache_eivec), cache_eival
785 | dZ = vec2mat(dz)
786 | dZ_tilde = U.T @ dZ @ U
787 | lambda_plus = np.maximum(lambda_var, 0.)
788 | lambda_minus = -np.minimum(lambda_var, 0.)
789 | m = len(lambda_plus)
790 | k = np.sum(lambda_plus > 0)
791 | # if not (np.sum(lambda_minus > 0) + k == m):
792 | # raise NonDifferentiable('SD cone projection not differentiable!')
793 | dS_tilde = np.empty((m, m))
794 | inner(m, lambda_plus, lambda_minus, dS_tilde, dZ_tilde)
795 | dS = U @ dS_tilde @ U.T
796 | return mat2vec(dS)
797 |
798 |
799 | @nb.jit(nb.float64[:](nb.float64[:], nb.float64[:],
800 | nb.float64[:]), nopython=True)
801 | def semidef_cone_Pi(z, cache_eivec, cache_eival):
802 |
803 | Z = vec2mat(z)
804 | eival, eivec = np.linalg.eigh(Z)
805 | result = mat2vec(eivec @ np.diag(np.maximum(eival, 0.)) @ eivec.T)
806 |
807 | Pi_Z = eivec @ np.diag(np.maximum(eival, 0.)) @ eivec.T
808 |
809 | # print('semidef proj Z = %s' % z)
810 | # Pi_star_Z = eivec @ np.diag(np.minimum(eival, 0.)) @ eivec.T
811 | # error = Pi_Z @ Pi_star_Z
812 | # print('eival = %s' % eival)
813 | # print('semidef_proj_error: %.2e' % np.linalg.norm(error))
814 |
815 | cache_eivec[:] = eivec.flatten()
816 | cache_eival[:] = eival
817 |
818 | return result
819 |
820 |
821 | semi_def_cone = cone(semidef_cone_Pi, semidef_cone_D, semidef_cone_D)
822 |
823 |
824 | @nb.jit(nb.float64[:](nb.float64[:], nb.float64[:],
825 | nb.types.Tuple((nb.float64[:], nb.float64[:]))))
826 | def semidef_cone_D_single_cache(z, dz, cache):
827 | return semidef_cone_D(z, dz, cache[0], cache[1])
828 |
829 |
830 | @nb.jit(nb.float64[:](nb.float64[:],
831 | nb.types.Tuple((nb.float64[:], nb.float64[:]))))
832 | def semidef_cone_Pi_single_cache(z, cache):
833 | return semidef_cone_Pi(z, cache[0], cache[1])
834 |
835 |
836 | # used as test harness for semi-def functions
837 | semi_def_cone_single_cache = cone(semidef_cone_Pi_single_cache, semidef_cone_D_single_cache,
838 | semidef_cone_D_single_cache)
839 |
840 | # these functions are used to store matrices in cache
841 |
842 |
843 | cache_types = nb.types.Tuple((nb.int64, nb.int64, # z, l
844 | nb.int64[:],
845 | nb.float64[:],
846 | # nb.types.List(nb.float64[:]), # q
847 | nb.int64[:], # s
848 | # nb.types.List(nb.float64[:, :]),
849 | nb.float64[:],
850 | # nb.types.List(nb.float64[:]),
851 | nb.float64[:],
852 | nb.int64, nb.float64[:, :], # ep
853 | nb.int64, nb.float64[:, :])) # ed
854 |
855 |
856 | # nb.float64[:](nb.float64[:], nb.float64[:], nb.int64, nb.int64, # z, l
857 | # nb.int64[:],
858 | # nb.float64[:],
859 | # # nb.types.List(nb.float64[:]), # q
860 | # nb.int64[:], # s
861 | # #nb.types.List(nb.float64[:, :]),
862 | # nb.float64[:],
863 | # # nb.types.List(nb.float64[:]),
864 | # nb.float64[:],
865 | # nb.int64,
866 | # nb.float64[:, :], # ep
867 | # nb.int64,
868 | # nb.float64[:, :]), nopython=True)
869 | @nb.jit(nopython=True)
870 | def prod_cone_D(z, x, zero, l, q, q_cache, s, s_cache_eivec, s_cache_eival, ep, ep_cache, ed, ed_cache):
871 |
872 | result = np.empty_like(z)
873 | cur = 0
874 |
875 | # zero
876 | result[cur:cur + zero] = zero_D(z[cur:cur + zero], x[cur:cur + zero])
877 | cur += zero
878 |
879 | # non-neg
880 | result[cur:cur + l] = non_neg_D(z[cur:cur + l], x[cur:cur + l])
881 | cur += l
882 |
883 | # second order
884 | sec_ord_cache_cur = 0
885 | for index, size in enumerate(q):
886 | result[cur:cur + size] = sec_ord_D(z[cur:cur + size],
887 | x[cur:cur + size],
888 | q_cache[sec_ord_cache_cur:
889 | sec_ord_cache_cur + size])
890 | cur += size
891 | sec_ord_cache_cur += size
892 |
893 | # semi-def
894 | semi_def_cache_cur = 0
895 | semi_def_eivec_cache_cur = 0
896 |
897 | for index, size in enumerate(s):
898 | vecsize = sizemat2sizevec(size)
899 | result[cur:cur + vecsize] = semidef_cone_D(z[cur:cur + vecsize],
900 | x[cur:cur + vecsize],
901 | s_cache_eivec[
902 | semi_def_eivec_cache_cur:semi_def_eivec_cache_cur + size**2],
903 | s_cache_eival[semi_def_cache_cur:semi_def_cache_cur + size])
904 |
905 | cur += vecsize
906 | semi_def_cache_cur += size
907 | semi_def_eivec_cache_cur += size**2
908 |
909 | # exp-pri
910 | for index in range(ep):
911 | result[cur:cur + 3] = exp_pri_D(z[cur:cur + 3],
912 | x[cur:cur + 3],
913 | ep_cache[index])
914 | cur += 3
915 |
916 | # exp-dua
917 | for index in range(ed):
918 | result[cur:cur + 3] = exp_dua_D(z[cur:cur + 3],
919 | x[cur:cur + 3],
920 | ed_cache[index])
921 | cur += 3
922 |
923 | assert cur == len(z)
924 | return result
925 |
926 |
927 | # @nb.jit(nb.float64[:](nb.float64[:], nb.int64, nb.int64, # z, l
928 | # nb.int64[:],
929 | # nb.float64[:],
930 | # # nb.types.List(nb.float64[:]), # q
931 | # nb.int64[:], # s
932 | # #nb.types.List(nb.float64[:, :]),
933 | # nb.float64[:],
934 | # # nb.types.List(nb.float64[:]),
935 | # nb.float64[:],
936 | # nb.int64, nb.float64[:, :], # ep
937 | # nb.int64, nb.float64[:, :]),
938 | @nb.jit(nopython=True)
939 | def prod_cone_Pi(z, zero, l, q, q_cache, s, s_cache_eivec, s_cache_eival, ep,
940 | ep_cache, ed, ed_cache):
941 | """Projection on product of zero, non-neg, sec. ord., sd, exp p., exp d."""
942 |
943 | result = np.empty_like(z)
944 | cur = 0
945 |
946 | # zero
947 | result[cur:cur + zero] = zero_Pi(z[cur:cur + zero])
948 | cur += zero
949 |
950 | # non-neg
951 | result[cur:cur + l] = non_neg_Pi(z[cur:cur + l])
952 | cur += l
953 |
954 | # second order
955 | sec_ord_cache_cur = 0
956 | for index, size in enumerate(q):
957 |
958 | result[cur:cur + size] = sec_ord_Pi(z[cur:cur + size],
959 | q_cache[sec_ord_cache_cur:
960 | sec_ord_cache_cur + size])
961 | cur += size
962 | sec_ord_cache_cur += size
963 |
964 | # semi-def
965 | semi_def_cache_cur = 0
966 | semi_def_eivec_cache_cur = 0
967 |
968 | for index, size in enumerate(s):
969 | vecsize = sizemat2sizevec(size)
970 | result[cur:cur + vecsize] = semidef_cone_Pi(z[cur:cur + vecsize],
971 | s_cache_eivec[semi_def_eivec_cache_cur:
972 | semi_def_eivec_cache_cur + size**2],
973 | s_cache_eival[semi_def_cache_cur:
974 | semi_def_cache_cur + size])
975 | cur += vecsize
976 | semi_def_cache_cur += size
977 | semi_def_eivec_cache_cur += size**2
978 |
979 | # exp-pri
980 | for index in range(ep):
981 | result[cur:cur + 3] = exp_pri_Pi(z[cur:cur + 3],
982 | ep_cache[index])
983 | cur += 3
984 |
985 | # exp-dua
986 | for index in range(ed):
987 | result[cur:cur + 3] = exp_dua_Pi(z[cur:cur + 3],
988 | ed_cache[index])
989 | cur += 3
990 |
991 | return result
992 |
993 |
994 | prod_cone = cone(prod_cone_Pi, prod_cone_D, prod_cone_D)
995 |
996 |
997 | def make_prod_cone_cache(dim_dict):
998 | return _make_prod_cone_cache(dim_dict['z'] if 'z' in dim_dict else 0,
999 | dim_dict['l'] if 'l' in dim_dict else 0,
1000 | np.array(dim_dict['q'] if 'q'
1001 | in dim_dict else [], dtype=np.int64),
1002 | np.array(dim_dict['s'] if 's' in
1003 | dim_dict else [], dtype=np.int64),
1004 | dim_dict['ep'] if 'ep' in dim_dict else 0,
1005 | dim_dict['ed'] if 'ed' in dim_dict else 0,)
1006 |
1007 |
1008 | # @nb.jit(cache_types(nb.int64, nb.int64, nb.int64[:], nb.int64[:],
1009 | # nb.int64, nb.int64), nopython=True)
1010 | @nb.jit(nopython=True)
1011 | def _make_prod_cone_cache(zero, non_neg, second_ord, semi_def, exp_pri, exp_dua):
1012 |
1013 | # q_cache = []
1014 | # for size in second_ord:
1015 | # q_cache.append(np.zeros(size))
1016 |
1017 | q_cache = np.zeros(np.sum(second_ord))
1018 |
1019 | # q_cache.append(np.zeros(1)) # for numba
1020 |
1021 | # s_cache_eivec = []
1022 | s_cache_eival = np.zeros(np.sum(semi_def))
1023 | s_cache_eivec = np.zeros(np.sum(semi_def**2))
1024 | # for matrix_size in semi_def:
1025 | # s_cache_eivec.append(np.zeros((matrix_size, matrix_size)))
1026 | # s_cache_eival.append(np.zeros(matrix_size))
1027 |
1028 | # s_cache_eivec.append(np.zeros((1, 1)))
1029 | # s_cache_eival.append(np.zeros(1))
1030 |
1031 | return zero, non_neg, second_ord, q_cache, \
1032 | semi_def, s_cache_eivec, s_cache_eival, \
1033 | exp_pri, np.zeros((exp_pri, 3)), \
1034 | exp_dua, np.zeros((exp_dua, 3))
1035 |
1036 |
1037 | # @nb.jit(nb.float64[:](nb.float64[:], nb.float64[:], cache_types, nb.int64),
1038 | # nopython=True)
1039 | #@nb.jit() # nopython=True)
1040 | # @nb.jit(nb.float64[:](nb.float64[:], nb.float64[:],
1041 | # nb.int64, nb.int64, # z, l
1042 | # nb.int64[:], nb.types.List(nb.float64[:]), # q
1043 | # nb.int64[:], # s
1044 | # nb.types.List(nb.float64[:, :]),
1045 | # nb.types.List(nb.float64[:]),
1046 | # nb.int64, nb.float64[:, :], # ep
1047 | # nb.int64, nb.float64[:, :],
1048 | # nb.int64), nopython=True)
1049 | @nb.jit(nopython=True)
1050 | def embedded_cone_D(z, dz, zero, l, q, q_cache, s, s_cache_eivec,
1051 | s_cache_eival, ep,
1052 | ep_cache, ed, ed_cache, n):
1053 | """Der. of proj. on the cone of the primal-dual embedding."""
1054 | # return dz - prod_cone.D(-z, dz, cache)
1055 | result = np.empty_like(dz)
1056 |
1057 | result[:n] = dz[:n]
1058 |
1059 | # ds = prod_cone_D(-z[n:-1], dz[n:-1], zero, l, q, q_cache, s, s_cache_eivec,
1060 | # s_cache_eival, ep, ep_cache, ed, ed_cache)
1061 | result[n:-1] = dz[n:-1] - prod_cone_D(-z[n:-1], dz[n:-1], zero, l, q,
1062 | q_cache, s, s_cache_eivec,
1063 | s_cache_eival, ep, ep_cache,
1064 | ed, ed_cache)
1065 | result[-1:] = non_neg_D(z[-1:], dz[-1:])
1066 |
1067 | return result
1068 |
1069 |
1070 | # @nb.jit(nb.float64[:](nb.float64[:], cache_types, nb.int64),
1071 | # nopython=True)
1072 | # @nb.jit(nopython=True)
1073 | # @nb.jit(nb.float64[:](nb.float64[:],
1074 | # nb.int64, nb.int64, # z, l
1075 | # nb.int64[:], nb.types.List(nb.float64[:]), # q
1076 | # nb.int64[:], # s
1077 | # nb.types.List(nb.float64[:, :]),
1078 | # nb.types.List(nb.float64[:]),
1079 | # nb.int64, nb.float64[:, :], # ep
1080 | # nb.int64, nb.float64[:, :],
1081 | # nb.int64), nopython=True)
1082 | @nb.jit(nopython=True)
1083 | def embedded_cone_Pi(z, zero, l, q, q_cache, s, s_cache_eivec, s_cache_eival,
1084 | ep, ep_cache, ed, ed_cache, n):
1085 | """Projection on the cone of the primal-dual embedding."""
1086 |
1087 | # emb_cones = [[zero_cone, n]] + cones + [[non_neg_cone, 1]]
1088 | # v, cache = prod_cone.Pi(-z, emb_cones)
1089 | # return v + z, cache
1090 |
1091 | result = np.empty_like(z)
1092 | result[:n] = z[:n] # x
1093 | result[n:-1] = z[n:-1] + prod_cone_Pi(-z[n:-1], zero, l, q, q_cache, s,
1094 | s_cache_eivec,
1095 | s_cache_eival, ep, ep_cache,
1096 | ed, ed_cache)
1097 | result[-1:] = non_neg_Pi(z[-1:]) # tau
1098 | return result
1099 |
1100 |
1101 | embedded_cone = cone(embedded_cone_Pi, embedded_cone_D, embedded_cone_D)
1102 |
--------------------------------------------------------------------------------
/cpr/cvxpy_interface.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright 2019 Enzo Busseti, Walaa Moursi, and Stephen Boyd
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | """
16 |
17 | import time
18 | import numpy as np
19 | import scipy.sparse as sp
20 | from scipy.sparse.linalg import LinearOperator
21 |
22 | from .refine import refine
23 | from .problem import residual_and_uv, uv2xsytaukappa, xsy2z
24 | from .cones import make_prod_cone_cache
25 |
26 | __all__ = ['cvxpy_solve', 'cvxpy_differentiate']
27 |
28 |
29 | def cvxpy_scs_to_cpr(data, sol=None):
30 |
31 | A, b, c, dims = data['A'], data['b'], data['c'], data['dims']
32 |
33 | if sol is None:
34 | z = np.zeros(len(b) + len(c) + 1)
35 | z[-1] = 1.
36 | else:
37 | z = xsy2z(sol['x'], sol['s'], sol['y'], tau=1., kappa=0.)
38 |
39 | if np.any(np.isnan(z)): # certificate...
40 |
41 | x = np.zeros_like(sol['x']) \
42 | if np.any(np.isnan(sol['x'])) else sol['x']
43 |
44 | s = np.zeros_like(sol['s']) \
45 | if np.any(np.isnan(sol['s'])) else sol['s']
46 |
47 | y = np.zeros_like(sol['y']) \
48 | if np.any(np.isnan(sol['y'])) else sol['y']
49 |
50 | if np.allclose(y, 0.) and c@x < 0:
51 | obj = c@x
52 | # assert obj < 0
53 | x /= -obj
54 | s /= -obj
55 | # print('primal res:', np.linalg.norm(A@x + s))
56 |
57 | if np.allclose(s, 0.) and b@y < 0:
58 | obj = b@y
59 | # assert obj < 0
60 | y /= -obj
61 | # print('dual res:', np.linalg.norm(A.T@y))
62 |
63 | z = xsy2z(x, s, y, tau=0., kappa=1.)
64 |
65 | dims_dict = {}
66 | if int(dims.nonpos):
67 | dims_dict['l'] = int(dims.nonpos)
68 | if int(dims.zero):
69 | dims_dict['z'] = int(dims.zero)
70 | if int(dims.exp):
71 | dims_dict['ep'] = int(dims.exp)
72 | if len(dims.soc):
73 | dims_dict['q'] = list([int(el) for el in dims.soc])
74 | if len(dims.psd):
75 | dims_dict['s'] = list([int(el) for el in dims.psd])
76 |
77 | return A, b, c, z, dims_dict
78 |
79 |
80 | def cvxpy_solve(cvxpy_problem, iters=2, lsqr_iters=30,
81 | presolve=False, scs_opts={},
82 | verbose=True, warm_start=True):
83 | from cvxpy.reductions.solvers.solving_chain import construct_solving_chain
84 |
85 | solving_chain = construct_solving_chain(cvxpy_problem, solver='SCS')
86 | data, inverse_data = solving_chain.apply(cvxpy_problem)
87 |
88 | start = time.time()
89 | if presolve:
90 | scs_solution = solving_chain.solve_via_data(cvxpy_problem,
91 | data=data,
92 | warm_start=warm_start,
93 | verbose=verbose,
94 | solver_opts=scs_opts)
95 |
96 | A, b, c, z, dims = cvxpy_scs_to_cpr(data, scs_solution)
97 | else:
98 | A, b, c, z, dims = cvxpy_scs_to_cpr(data)
99 | scs_solution = {}
100 | if warm_start and 'CPR' in cvxpy_problem._solver_cache:
101 | z = cvxpy_problem._solver_cache['CPR']['z']
102 | prepare_time = time.time() - start
103 |
104 | # TODO change this
105 |
106 | start = time.time()
107 | refined_z = refine(A, b, c, dims, z, iters=iters,
108 | lsqr_iters=lsqr_iters, verbose=verbose)
109 | cvxpy_problem._solver_cache['CPR'] = {'z': refined_z}
110 | refine_time = time.time() - start
111 |
112 | new_residual, u, v = residual_and_uv(
113 | refined_z, (A.indptr, A.indices, A.data), b, c, make_prod_cone_cache(dims))
114 |
115 | scs_solution['x'], scs_solution['s'], scs_solution[
116 | 'y'], tau, kappa = uv2xsytaukappa(u, v, A.shape[1])
117 |
118 | scs_solution["info"] = {'status': 'Solved', 'solveTime': refine_time,
119 | 'setupTime': prepare_time, 'iter': iters, 'pobj': scs_solution['x'] @ c if tau > 0 else np.nan}
120 |
121 | cvxpy_problem.unpack_results(scs_solution, solving_chain, inverse_data)
122 |
123 | return cvxpy_problem.value
124 |
125 |
126 | class NotAffine(Exception):
127 | pass
128 |
129 |
130 | def cvxpy_differentiate(cvxpy_problem, parameters, output_expression,
131 | iters=2, lsqr_iters=30, derive_lsqr_iters=100,
132 | presolve=False, scs_opts={},
133 | verbose=True, warm_start=True):
134 | """Compute the derivative matrix of the solution map of
135 | a CVXPY problem, whose input is a list of CVXPY parameters,
136 | and output is a CVXPY one-dimensional expression of the solution,
137 | the constraint violations, or the dual parameters.
138 |
139 | Only affine CVXPY transformations are allowed.
140 | """
141 |
142 | solving_chain = construct_solving_chain(cvxpy_problem, solver='SCS')
143 | data, inverse_data = solving_chain.apply(cvxpy_problem)
144 | A, b, c, _, _ = cvxpy_scs_to_cpr(data)
145 |
146 | # A is a sparse matrix, so below we compute
147 | # sparse matrix differences.
148 |
149 | input_mappings = []
150 |
151 | # make mapping from input parameters to data
152 | for parameter in parameters:
153 | if verbose:
154 | print('compiling parameter', parameter.name())
155 | old_par_val = parameter.value
156 | parameter.value += 1.
157 | newdata, _ = solving_chain.apply(cvxpy_problem)
158 | new_A, new_b, new_c, _, _ = cvxpy_scs_to_cpr(newdata)
159 | dA = new_A - A
160 | db = new_b - b
161 | dc = new_c - c
162 | parameter.value -= 2.
163 | newdata, _ = solving_chain.apply(cvxpy_problem)
164 | new_A, new_b, new_c, _, _ = cvxpy_scs_to_cpr(newdata)
165 | if not (np.allclose(A - new_A, dA)) \
166 | and (np.allclose(b - new_b, db))\
167 | and (np.allclose(c - new_c, dc)):
168 | raise NotAffine('on parameter %s' % parameter.name())
169 | parameter.value = old_par_val
170 | input_mappings.append((dA, db, dc))
171 |
172 | _ = cvxpy_solve(cvxpy_problem, iters=iters, lsqr_iters=lsqr_iters,
173 | presolve=presolve, scs_opts=scs_opts,
174 | verbose=verbose, warm_start=warm_start)
175 |
176 | # used by cvxpy to transform back
177 | scs_solution = {}
178 | scs_solution["info"] = {'status': 'Solved', 'solveTime': 0.,
179 | 'setupTime': 0., 'iter': 0, 'pobj': np.nan}
180 |
181 | z = cvxpy_problem._solver_cache['CPR']['z']
182 |
183 | if not (len(output_expression.shape) == 1):
184 | raise ValueError('Only one-dimensional outputs')
185 | output_matrix = np.empty((len(z), output_expression.size))
186 | base = output_expression.value
187 |
188 | # make mapping from z to output
189 | for i in range(len(z)):
190 | # perturb z
191 | old_val = z[i]
192 | z[i] += old_val * 1e-8
193 | _, new_u, new_v = residual_and_uv(
194 | z, (A.indptr, A.indices, A.data), b, c,
195 | make_prod_cone_cache(dims))
196 | scs_solution['x'], scs_solution['s'], scs_solution[
197 | 'y'], tau, kappa = uv2xsytaukappa(new_u, new_v, A.shape[1])
198 | cvxpy_problem.unpack_results(scs_solution, solving_chain,
199 | inverse_data)
200 |
201 | output_matrix[i, :] = output_expression.value - base
202 |
203 | z[i] -= old_val * 2e-8
204 | _, new_u, new_v = residual_and_uv(
205 | z, (A.indptr, A.indices, A.data), b, c,
206 | make_prod_cone_cache(dims))
207 | scs_solution['x'], scs_solution['s'], scs_solution[
208 | 'y'], tau, kappa = uv2xsytaukappa(new_u, new_v, A.shape[1])
209 | cvxpy_problem.unpack_results(scs_solution, solving_chain,
210 | inverse_data)
211 |
212 | if not np.allclose(output_matrix[i, :],
213 | base - output_expression.value):
214 | raise NotAffine('on solution variable z[%d]' % i)
215 |
216 | z[i] = old_val
217 |
218 | def matvec(d_parameters):
219 | assert len(d_parameters) == len(parameters)
220 | total_dA = sp.csc_matrix()
221 | total_db = np.zeros(len(b))
222 | total_dc = np.zeros(len(c))
223 |
224 | for i in len(d_parameters):
225 | total_dA += input_mappings[i][0] * d_parameters[i]
226 | total_db += input_mappings[i][1] * d_parameters[i]
227 | total_dc += input_mappings[i][2] * d_parameters[i]
228 |
229 | refined_z = refine(A + total_dA, b + total_db,
230 | c + total_dc, dims, z, iters=1,
231 | lsqr_iters=derive_lsqr_iters, verbose=verbose)
232 | dz = refined_z - z
233 | return dz @ output_matrix
234 |
235 | def rmatvec(d_output):
236 | dz = output_matrix @ d_output
237 |
238 | result = LinearOperator((len(parameters), output_expression.size),
239 | matvec=matvec,
240 | rmatvec=rmatvec,
241 | dtype=np.float)
242 |
--------------------------------------------------------------------------------
/cpr/lsqr.py:
--------------------------------------------------------------------------------
1 | """Sparse Equations and Least Squares.
2 |
3 | The original Fortran code was written by C. C. Paige and M. A. Saunders as
4 | described in
5 |
6 | C. C. Paige and M. A. Saunders, LSQR: An algorithm for sparse linear
7 | equations and sparse least squares, TOMS 8(1), 43--71 (1982).
8 |
9 | C. C. Paige and M. A. Saunders, Algorithm 583; LSQR: Sparse linear
10 | equations and least-squares problems, TOMS 8(2), 195--209 (1982).
11 |
12 | It is licensed under the following BSD license:
13 |
14 | Copyright (c) 2006, Systems Optimization Laboratory
15 | All rights reserved.
16 |
17 | Redistribution and use in source and binary forms, with or without
18 | modification, are permitted provided that the following conditions are
19 | met:
20 |
21 | * Redistributions of source code must retain the above copyright
22 | notice, this list of conditions and the following disclaimer.
23 |
24 | * Redistributions in binary form must reproduce the above
25 | copyright notice, this list of conditions and the following
26 | disclaimer in the documentation and/or other materials provided
27 | with the distribution.
28 |
29 | * Neither the name of Stanford University nor the names of its
30 | contributors may be used to endorse or promote products derived
31 | from this software without specific prior written permission.
32 |
33 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
34 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
35 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
36 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
37 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
38 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
39 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
40 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
41 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
42 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
43 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
44 |
45 | The Fortran code was translated to Python for use in CVXOPT by Jeffery
46 | Kline with contributions by Mridul Aanjaneya and Bob Myhill.
47 |
48 | Adapted for SciPy by Stefan van der Walt.
49 |
50 | """
51 |
52 | from __future__ import division, print_function, absolute_import
53 |
54 | __all__ = ['lsqr']
55 |
56 | import numpy as np
57 | from math import sqrt
58 | # from scipy.sparse.linalg.interface import aslinearoperator
59 |
60 | eps = np.finfo(np.float64).eps
61 | import numba as nb
62 |
63 | from .problem import lsqr_D, lsqr_DT, CSC_mattypes, residual_and_uv
64 | from .cones import cache_types
65 |
66 |
67 | @nb.jit(nopython=True)
68 | def _sym_ortho(a, b):
69 | """
70 | Stable implementation of Givens rotation.
71 |
72 | Notes
73 | -----
74 | The routine 'SymOrtho' was added for numerical stability. This is
75 | recommended by S.-C. Choi in [1]_. It removes the unpleasant potential of
76 | ``1/eps`` in some important places (see, for example text following
77 | "Compute the next plane rotation Qk" in minres.py).
78 |
79 | References
80 | ----------
81 | .. [1] S.-C. Choi, "Iterative Methods for Singular Linear Equations
82 | and Least-Squares Problems", Dissertation,
83 | http://www.stanford.edu/group/SOL/dissertations/sou-cheng-choi-thesis.pdf
84 |
85 | """
86 | if b == 0:
87 | return np.sign(a), 0, abs(a)
88 | elif a == 0:
89 | return 0, np.sign(b), abs(b)
90 | elif abs(b) > abs(a):
91 | tau = a / b
92 | s = np.sign(b) / sqrt(1 + tau * tau)
93 | c = s * tau
94 | r = b / s
95 | else:
96 | tau = b / a
97 | c = np.sign(a) / sqrt(1 + tau * tau)
98 | s = c * tau
99 | r = a / c
100 | return c, s, r
101 |
102 |
103 | # operator_vars_types = nb.types.Tuple((nb.float64[:],
104 | # CSC_mattypes,
105 | # nb.float64[:],
106 | # nb.float64[:],
107 | # cache_types,
108 | # nb.float64[:]))
109 |
110 |
111 | # @nb.jit(nb.types.Tuple((nb.float64[:], nb.int64,
112 | # nb.float64, nb.float64))(
113 | # nb.int64, nb.int64, operator_vars_types,
114 | # nb.float64[:],
115 | # nb.optional(nb.float64),
116 | # nb.optional(nb.float64),
117 | # nb.optional(nb.float64),
118 | # nb.optional(nb.float64),
119 | # nb.optional(nb.int64)), nopython=True)
120 | @nb.jit(nopython=True)
121 | def lsqr(m, n, operator_vars, b, damp=1E-6, atol=1e-8, btol=1e-8, conlim=1e8,
122 | iter_lim=1000):
123 | show = False
124 | calc_var = False
125 | x0 = None
126 | """Find the least-squares solution to a large, sparse, linear system
127 | of equations.
128 |
129 | The function solves ``Ax = b`` or ``min ||b - Ax||^2`` or
130 | ``min ||Ax - b||^2 + d^2 ||x||^2``.
131 |
132 | The matrix A may be square or rectangular (over-determined or
133 | under-determined), and may have any rank.
134 |
135 | ::
136 |
137 | 1. Unsymmetric equations -- solve A*x = b
138 |
139 | 2. Linear least squares -- solve A*x = b
140 | in the least-squares sense
141 |
142 | 3. Damped least squares -- solve ( A )*x = ( b )
143 | ( damp*I ) ( 0 )
144 | in the least-squares sense
145 |
146 | Parameters
147 | ----------
148 | A : {sparse matrix, ndarray, LinearOperator}
149 | Representation of an m-by-n matrix. It is required that
150 | the linear operator can produce ``Ax`` and ``A^T x``.
151 | b : array_like, shape (m,)
152 | Right-hand side vector ``b``.
153 | damp : float
154 | Damping coefficient.
155 | atol, btol : float, optional
156 | Stopping tolerances. If both are 1.0e-9 (say), the final
157 | residual norm should be accurate to about 9 digits. (The
158 | final x will usually have fewer correct digits, depending on
159 | cond(A) and the size of damp.)
160 | conlim : float, optional
161 | Another stopping tolerance. lsqr terminates if an estimate of
162 | ``cond(A)`` exceeds `conlim`. For compatible systems ``Ax =
163 | b``, `conlim` could be as large as 1.0e+12 (say). For
164 | least-squares problems, conlim should be less than 1.0e+8.
165 | Maximum precision can be obtained by setting ``atol = btol =
166 | conlim = zero``, but the number of iterations may then be
167 | excessive.
168 | iter_lim : int, optional
169 | Explicit limitation on number of iterations (for safety).
170 | show : bool, optional
171 | Display an iteration log.
172 | calc_var : bool, optional
173 | Whether to estimate diagonals of ``(A'A + damp^2*I)^{-1}``.
174 | x0 : array_like, shape (n,), optional
175 | Initial guess of x, if None zeros are used.
176 |
177 | .. versionadded:: 1.0.0
178 |
179 | Returns
180 | -------
181 | x : ndarray of float
182 | The final solution.
183 | istop : int
184 | Gives the reason for termination.
185 | 1 means x is an approximate solution to Ax = b.
186 | 2 means x approximately solves the least-squares problem.
187 | itn : int
188 | Iteration number upon termination.
189 | r1norm : float
190 | ``norm(r)``, where ``r = b - Ax``.
191 | r2norm : float
192 | ``sqrt( norm(r)^2 + damp^2 * norm(x)^2 )``. Equal to `r1norm` if
193 | ``damp == 0``.
194 | anorm : float
195 | Estimate of Frobenius norm of ``Abar = [[A]; [damp*I]]``.
196 | acond : float
197 | Estimate of ``cond(Abar)``.
198 | arnorm : float
199 | Estimate of ``norm(A'*r - damp^2*x)``.
200 | xnorm : float
201 | ``norm(x)``
202 | var : ndarray of float
203 | If ``calc_var`` is True, estimates all diagonals of
204 | ``(A'A)^{-1}`` (if ``damp == 0``) or more generally ``(A'A +
205 | damp^2*I)^{-1}``. This is well defined if A has full column
206 | rank or ``damp > 0``. (Not sure what var means if ``rank(A)
207 | < n`` and ``damp = 0.``)
208 |
209 | Notes
210 | -----
211 | LSQR uses an iterative method to approximate the solution. The
212 | number of iterations required to reach a certain accuracy depends
213 | strongly on the scaling of the problem. Poor scaling of the rows
214 | or columns of A should therefore be avoided where possible.
215 |
216 | For example, in problem 1 the solution is unaltered by
217 | row-scaling. If a row of A is very small or large compared to
218 | the other rows of A, the corresponding row of ( A b ) should be
219 | scaled up or down.
220 |
221 | In problems 1 and 2, the solution x is easily recovered
222 | following column-scaling. Unless better information is known,
223 | the nonzero columns of A should be scaled so that they all have
224 | the same Euclidean norm (e.g., 1.0).
225 |
226 | In problem 3, there is no freedom to re-scale if damp is
227 | nonzero. However, the value of damp should be assigned only
228 | after attention has been paid to the scaling of A.
229 |
230 | The parameter damp is intended to help regularize
231 | ill-conditioned systems, by preventing the true solution from
232 | being very large. Another aid to regularization is provided by
233 | the parameter acond, which may be used to terminate iterations
234 | before the computed solution becomes very large.
235 |
236 | If some initial estimate ``x0`` is known and if ``damp == 0``,
237 | one could proceed as follows:
238 |
239 | 1. Compute a residual vector ``r0 = b - A*x0``.
240 | 2. Use LSQR to solve the system ``A*dx = r0``.
241 | 3. Add the correction dx to obtain a final solution ``x = x0 + dx``.
242 |
243 | This requires that ``x0`` be available before and after the call
244 | to LSQR. To judge the benefits, suppose LSQR takes k1 iterations
245 | to solve A*x = b and k2 iterations to solve A*dx = r0.
246 | If x0 is "good", norm(r0) will be smaller than norm(b).
247 | If the same stopping tolerances atol and btol are used for each
248 | system, k1 and k2 will be similar, but the final solution x0 + dx
249 | should be more accurate. The only way to reduce the total work
250 | is to use a larger stopping tolerance for the second system.
251 | If some value btol is suitable for A*x = b, the larger value
252 | btol*norm(b)/norm(r0) should be suitable for A*dx = r0.
253 |
254 | Preconditioning is another way to reduce the number of iterations.
255 | If it is possible to solve a related system ``M*x = b``
256 | efficiently, where M approximates A in some helpful way (e.g. M -
257 | A has low rank or its elements are small relative to those of A),
258 | LSQR may converge more rapidly on the system ``A*M(inverse)*z =
259 | b``, after which x can be recovered by solving M*x = z.
260 |
261 | If A is symmetric, LSQR should not be used!
262 |
263 | Alternatives are the symmetric conjugate-gradient method (cg)
264 | and/or SYMMLQ. SYMMLQ is an implementation of symmetric cg that
265 | applies to any symmetric A and will converge more rapidly than
266 | LSQR. If A is positive definite, there are other implementations
267 | of symmetric cg that require slightly less work per iteration than
268 | SYMMLQ (but will take the same number of iterations).
269 |
270 | References
271 | ----------
272 | .. [1] C. C. Paige and M. A. Saunders (1982a).
273 | "LSQR: An algorithm for sparse linear equations and
274 | sparse least squares", ACM TOMS 8(1), 43-71.
275 | .. [2] C. C. Paige and M. A. Saunders (1982b).
276 | "Algorithm 583. LSQR: Sparse linear equations and least
277 | squares problems", ACM TOMS 8(2), 195-209.
278 | .. [3] M. A. Saunders (1995). "Solution of sparse rectangular
279 | systems using LSQR and CRAIG", BIT 35, 588-604.
280 |
281 | Examples
282 | --------
283 | >>> from scipy.sparse import csc_matrix
284 | >>> from scipy.sparse.linalg import lsqr
285 | >>> A = csc_matrix([[1., 0.], [1., 1.], [0., 1.]], dtype=float)
286 |
287 | The first example has the trivial solution `[0, 0]`
288 |
289 | >>> b = np.array([0., 0., 0.], dtype=float)
290 | >>> x, istop, itn, normr = lsqr(A, b)[:4]
291 | The exact solution is x = 0
292 | >>> istop
293 | 0
294 | >>> x
295 | array([ 0., 0.])
296 |
297 | The stopping code `istop=0` returned indicates that a vector of zeros was
298 | found as a solution. The returned solution `x` indeed contains `[0., 0.]`.
299 | The next example has a non-trivial solution:
300 |
301 | >>> b = np.array([1., 0., -1.], dtype=float)
302 | >>> x, istop, itn, r1norm = lsqr(A, b)[:4]
303 | >>> istop
304 | 1
305 | >>> x
306 | array([ 1., -1.])
307 | >>> itn
308 | 1
309 | >>> r1norm
310 | 4.440892098500627e-16
311 |
312 | As indicated by `istop=1`, `lsqr` found a solution obeying the tolerance
313 | limits. The given solution `[1., -1.]` obviously solves the equation. The
314 | remaining return values include information about the number of iterations
315 | (`itn=1`) and the remaining difference of left and right side of the solved
316 | equation.
317 | The final example demonstrates the behavior in the case where there is no
318 | solution for the equation:
319 |
320 | >>> b = np.array([1., 0.01, -1.], dtype=float)
321 | >>> x, istop, itn, r1norm = lsqr(A, b)[:4]
322 | >>> istop
323 | 2
324 | >>> x
325 | array([ 1.00333333, -0.99666667])
326 | >>> A.dot(x)-b
327 | array([ 0.00333333, -0.00333333, 0.00333333])
328 | >>> r1norm
329 | 0.005773502691896255
330 |
331 | `istop` indicates that the system is inconsistent and thus `x` is rather an
332 | approximate solution to the corresponding least-squares problem. `r1norm`
333 | contains the norm of the minimal residual that was found.
334 | """
335 | # A = aslinearoperator(A)
336 |
337 | my_z, A, my_b, my_c, cones_caches, residual = operator_vars
338 |
339 | def matvec(dz):
340 | return lsqr_D(my_z, dz, A, my_b, my_c,
341 | cones_caches, residual)
342 |
343 | def rmatvec(dres):
344 | return lsqr_DT(my_z, dres, A, my_b,
345 | my_c, cones_caches, residual)
346 |
347 | # b = np.atleast_1d(b)
348 | # if b.ndim > 1:
349 | # b = b.squeeze()
350 |
351 | # m, n = A.shape
352 | # if iter_lim is None:
353 | # iter_lim = 2 * n
354 |
355 | iter_lim = min(iter_lim, 2 * n)
356 |
357 | var = np.zeros(n)
358 |
359 | msg = ('The exact solution is x = 0 ',
360 | 'Ax - b is small enough, given atol, btol ',
361 | 'The least-squares solution is good enough, given atol ',
362 | 'The estimate of cond(Abar) has exceeded conlim ',
363 | 'Ax - b is small enough for this machine ',
364 | 'The least-squares solution is good enough for this machine',
365 | 'Cond(Abar) seems to be too large for this machine ',
366 | 'The iteration limit has been reached ')
367 |
368 | # if show:
369 | # # with objmode(): # disables numba C code generation to use python
370 | # # pretty print
371 | # print(' ')
372 | # print('LSQR Least-squares solution of Ax = b')
373 | # print('The matrix A has %d rows and %d cols' % (m, n))
374 | # print('damp = %20.14e calc_var = %8g' % (damp, calc_var))
375 | # print('atol = %8.2e conlim = %8.2e' % (
376 | # atol, conlim))
377 | # print('btol = %8.2e iter_lim = %8g' % (
378 | # btol, iter_lim))
379 |
380 | itn = 0
381 | istop = 0
382 | ctol = 0
383 | if conlim > 0:
384 | ctol = 1 / conlim
385 | anorm = 0
386 | acond = 0
387 | dampsq = damp**2
388 | ddnorm = 0
389 | res2 = 0
390 | xnorm = 0
391 | xxnorm = 0
392 | z = 0
393 | cs2 = -1
394 | sn2 = 0
395 |
396 | """
397 | Set up the first vectors u and v for the bidiagonalization.
398 | These satisfy beta*u = b - A*x, alfa*v = A'*u.
399 | """
400 | u = b
401 | bnorm = np.linalg.norm(b)
402 |
403 | # if x0 is None:
404 | x = np.zeros(n)
405 | beta = float(bnorm)
406 |
407 | # else:
408 | # x = np.copy(x0)
409 | # u = u - matvec(x)
410 | # beta = np.linalg.norm(u)
411 |
412 | if beta > 0:
413 | u = (1. / beta) * u
414 | v = rmatvec(u)
415 | alfa = np.linalg.norm(v)
416 | else:
417 | v = np.copy(x)
418 | alfa = 0
419 |
420 | if alfa > 0:
421 | v /= alfa
422 | w = np.copy(v)
423 |
424 | rhobar = alfa
425 | phibar = beta
426 | rnorm = beta
427 | r1norm = rnorm
428 | r2norm = rnorm
429 |
430 | # Reverse the order here from the original matlab code because
431 | # there was an error on return when arnorm==0
432 | arnorm = alfa * beta
433 | if arnorm == 0:
434 | # print(msg[0])
435 | return x, itn, r1norm, acond
436 | # return x, istop, itn, r1norm, r2norm, anorm, acond, arnorm, xnorm,
437 | # var
438 |
439 | head1 = ' Itn x[0] r1norm r2norm '
440 | head2 = ' Compatible LS Norm A Cond A'
441 |
442 | # if show:
443 | # # with objmode():
444 | # print(' ')
445 | # print(head1, head2)
446 | # print('%6g %12.5e' % (itn, x[0]),
447 | # ' %10.3e %10.3e' % (r1norm, r2norm),
448 | # ' %8.1e %8.1e' % (1, alfa / beta))
449 |
450 | # Main iteration loop.
451 | while itn < iter_lim:
452 |
453 | itn = itn + 1
454 |
455 | # every 30 iters, update the derivative
456 | # if itn % 20 == 19:
457 | # print('checking residual')
458 | # tmp_residual, _, _ = residual_and_uv(
459 | # my_z - x, A, my_b, my_c, cones_caches)
460 | # if np.linalg.norm(tmp_residual) / np.abs((my_z - x)[-1]) > r1norm:
461 | # break
462 |
463 | # def matvec(dz):
464 | # return lsqr_D(my_z, dz, A, my_b, my_c,
465 | # cones_caches, residual)
466 |
467 | # def rmatvec(dres):
468 | # return lsqr_DT(my_z, dres, A, my_b,
469 | # my_c, cones_caches, residual)
470 |
471 | """
472 | % Perform the next step of the bidiagonalization to obtain the
473 | % next beta, u, alfa, v. These satisfy the relations
474 | % beta*u = a*v - alfa*u,
475 | % alfa*v = A'*u - beta*v.
476 | """
477 | u = matvec(v) - alfa * u
478 | beta = np.linalg.norm(u)
479 |
480 | if beta > 0:
481 | u /= beta
482 | anorm = sqrt(anorm**2 + alfa**2 + beta**2 + damp**2)
483 | v = rmatvec(u) - beta * v
484 | alfa = np.linalg.norm(v)
485 | if alfa > 0:
486 | v /= alfa
487 |
488 | # Use a plane rotation to eliminate the damping parameter.
489 | # This alters the diagonal (rhobar) of the lower-bidiagonal matrix.
490 | rhobar1 = sqrt(rhobar**2 + damp**2)
491 | cs1 = rhobar / rhobar1
492 | sn1 = damp / rhobar1
493 | psi = sn1 * phibar
494 | phibar = cs1 * phibar
495 |
496 | # Use a plane rotation to eliminate the subdiagonal element (beta)
497 | # of the lower-bidiagonal matrix, giving an upper-bidiagonal matrix.
498 | cs, sn, rho = _sym_ortho(rhobar1, beta)
499 |
500 | theta = sn * alfa
501 | rhobar = -cs * alfa
502 | phi = cs * phibar
503 | phibar = sn * phibar
504 | tau = sn * phi
505 |
506 | # Update x and w.
507 | t1 = phi / rho
508 | t2 = -theta / rho
509 | dk = (1. / rho) * w
510 |
511 | x = x + t1 * w
512 | w = v + t2 * w
513 | ddnorm = ddnorm + np.linalg.norm(dk)**2
514 |
515 | if calc_var:
516 | var = var + dk**2
517 |
518 | # Use a plane rotation on the right to eliminate the
519 | # super-diagonal element (theta) of the upper-bidiagonal matrix.
520 | # Then use the result to estimate norm(x).
521 | delta = sn2 * rho
522 | gambar = -cs2 * rho
523 | rhs = phi - delta * z
524 | zbar = rhs / gambar
525 | xnorm = sqrt(xxnorm + zbar**2)
526 | gamma = sqrt(gambar**2 + theta**2)
527 | cs2 = gambar / gamma
528 | sn2 = theta / gamma
529 | z = rhs / gamma
530 | xxnorm = xxnorm + z**2
531 |
532 | # Test for convergence.
533 | # First, estimate the condition of the matrix Abar,
534 | # and the norms of rbar and Abar'rbar.
535 | acond = anorm * sqrt(ddnorm)
536 | res1 = phibar**2
537 | res2 = res2 + psi**2
538 | rnorm = sqrt(res1 + res2)
539 | arnorm = alfa * abs(tau)
540 |
541 | # Distinguish between
542 | # r1norm = ||b - Ax|| and
543 | # r2norm = rnorm in current code
544 | # = sqrt(r1norm^2 + damp^2*||x||^2).
545 | # Estimate r1norm from
546 | # r1norm = sqrt(r2norm^2 - damp^2*||x||^2).
547 | # Although there is cancellation, it might be accurate enough.
548 | r1sq = rnorm**2 - dampsq * xxnorm
549 | r1norm = sqrt(abs(r1sq))
550 | if r1sq < 0:
551 | r1norm = -r1norm
552 | r2norm = rnorm
553 |
554 | # Now use these norms to estimate certain other quantities,
555 | # some of which will be small near a solution.
556 | test1 = rnorm / bnorm
557 | test2 = arnorm / (anorm * rnorm + eps)
558 | test3 = 1 / (acond + eps)
559 | t1 = test1 / (1 + anorm * xnorm / bnorm)
560 | rtol = btol + atol * anorm * xnorm / bnorm
561 |
562 | # The following tests guard against extremely small values of
563 | # atol, btol or ctol. (The user may have set any or all of
564 | # the parameters atol, btol, conlim to 0.)
565 | # The effect is equivalent to the normal tests using
566 | # atol = eps, btol = eps, conlim = 1/eps.
567 | if itn >= iter_lim:
568 | istop = 7
569 | if 1 + test3 <= 1:
570 | istop = 6
571 | if 1 + test2 <= 1:
572 | istop = 5
573 | if 1 + t1 <= 1:
574 | istop = 4
575 |
576 | # Allow for tolerances set by the user.
577 | if test3 <= ctol:
578 | istop = 3
579 | if test2 <= atol:
580 | istop = 2
581 | if test1 <= rtol:
582 | istop = 1
583 |
584 | # See if it is time to print something.
585 | # prnt = False
586 | # if n <= 40:
587 | # prnt = True
588 | # if itn <= 10:
589 | # prnt = True
590 | # if itn >= iter_lim - 10:
591 | # prnt = True
592 | # # if itn%10 == 0: prnt = True
593 | # if test3 <= 2 * ctol:
594 | # prnt = True
595 | # if test2 <= 10 * atol:
596 | # prnt = True
597 | # if test1 <= 10 * rtol:
598 | # prnt = True
599 | # if istop != 0:
600 | # prnt = True
601 |
602 | # if prnt:
603 | # if show:
604 | # pass
605 | # str1 = '%6g %12.5e' % (itn, x[0])
606 | # str2 = ' %10.3e %10.3e' % (r1norm, r2norm)
607 | # str3 = ' %8.1e %8.1e' % (test1, test2)
608 | # str4 = ' %8.1e %8.1e' % (anorm, acond)
609 | # print(str1, str2, str3, str4)
610 |
611 | if istop != 0:
612 | break
613 |
614 | # End of iteration loop.
615 | # Print the stopping condition.
616 | # if show:
617 | # # pass
618 | # print(' ')
619 | # print('LSQR finished')
620 | # print(msg[istop])
621 | # print(' ')
622 | # str1 = 'istop =%8g r1norm =%8.1e' % (istop, r1norm)
623 | # str2 = 'anorm =%8.1e arnorm =%8.1e' % (anorm, arnorm)
624 | # str3 = 'itn =%8g r2norm =%8.1e' % (itn, r2norm)
625 | # str4 = 'acond =%8.1e xnorm =%8.1e' % (acond, xnorm)
626 | # print(str1 + ' ' + str2)
627 | # print(str3 + ' ' + str4)
628 | # print(' ')
629 |
630 | return x, itn, r1norm, acond
631 | # return x, istop, itn, r1norm, r2norm, anorm, acond, arnorm, xnorm, var
632 |
--------------------------------------------------------------------------------
/cpr/problem.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright 2019 Enzo Busseti, Walaa Moursi, and Stephen Boyd
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | """
16 |
17 | import time
18 | import numpy as np
19 | from numpy.linalg import norm
20 | import scipy.sparse as sp
21 | import numba as nb
22 |
23 | # from scipy.sparse.linalg import lsqr, LinearOperator
24 | # from .lsqr import lsqr
25 |
26 | import scipy.sparse as sp
27 |
28 | from .cones import prod_cone
29 | from .utils import *
30 | from .sparse_matvec import *
31 |
32 |
33 | # csc_matvec(A_csc.shape[0], A_csc.indptr, A_csc.indices, A_csc.data, b)
34 |
35 | #@nb.jit(nopython=True)
36 |
37 |
38 | #@nb.jit(nb.types.Tuple((nb.float64[:], nb.float64[:]))(
39 | # nb.float64[:], nb.float64[:], nb.float64[:],
40 | # nb.optional(nb.float64), nb.optional(nb.float64)), nopython=True)
41 | @nb.jit(nopython=True)
42 | def xsy2uv(x, s, y, tau=1., kappa=0.):
43 | n = len(x)
44 | m = len(s)
45 | u = np.empty(m + n + 1)
46 | v = np.empty_like(u)
47 | u[:n] = x
48 | u[n:-1] = y
49 | u[-1] = tau
50 | v[:n] = 0
51 | v[n:-1] = s
52 | v[-1] = kappa
53 | return u, v
54 |
55 |
56 | #@nb.jit(nopython=True)
57 | @nb.jit(nb.float64[:](
58 | nb.float64[:], nb.float64[:], nb.float64[:],
59 | nb.optional(nb.float64), nb.optional(nb.float64)),
60 | nopython=True)
61 | def xsy2z(x, s, y, tau=1., kappa=0.):
62 | u, v = xsy2uv(x, s, y, tau, kappa)
63 | return u - v
64 |
65 |
66 | @nb.jit(nopython=True)
67 | def uv2xsytaukappa(u, v, n):
68 | tau = np.float(u[-1])
69 | kappa = np.float(v[-1])
70 | x = u[:n] / tau if tau > 0 else u[:n] / kappa
71 | y = u[n:-1] / tau if tau > 0 else u[n:-1] / kappa
72 | s = v[n:-1] / tau if tau > 0 else v[n:-1] / kappa
73 | return x, s, y, tau, kappa
74 |
75 | # @nb.jit(nopython=True)
76 |
77 |
78 | @nb.jit(nopython=True)
79 | def z2uv(z, n, cones):
80 | u, cache = embedded_cone_Pi(z, *cones, n)
81 | return u, u - z, cache
82 |
83 |
84 | @nb.jit(nopython=True)
85 | def z2xsy(z, n, cones):
86 | # TODO implement infeasibility cert.
87 | u, cache = embedded_cone_Pi(z, *cones, n)
88 | v = u - z
89 | x, s, y, tau, kappa = uv2xsytaukappa(u, v, n)
90 | return x, s, y
91 |
92 |
93 | CSC_mattypes = nb.types.Tuple((nb.int32[:], nb.int32[:], nb.float64[:]))
94 |
95 | #@nb.jit(nopython=True)
96 |
97 |
98 | @nb.jit(nb.float64[:](
99 | CSC_mattypes,
100 | # CSC_mattypes,
101 | nb.float64[:],
102 | nb.float64[:],
103 | nb.float64[:]), nopython=True)
104 | def Q_matvec(A, b, c, u):
105 | col_pointers, row_indeces, mat_elements = A
106 | m, n = len(b), len(c)
107 | result = np.empty_like(u)
108 | result[:n] = csr_matvec(col_pointers, row_indeces, mat_elements, u[n:-1]) \
109 | + c * u[-1]
110 | # result[:n] = A_tr@u[n:-1] + c * u[-1]
111 | # result[n:-1] = -A@u[:n] + b * u[-1]
112 | result[n:-1] = - csc_matvec(m, col_pointers, row_indeces, mat_elements, u[:n])\
113 | + b * u[-1]
114 | result[-1] = -c.T@u[:n] - b.T@u[n:-1]
115 | return result
116 |
117 |
118 | #@nb.jit(nopython=True)
119 | @nb.jit(nb.float64[:](
120 | CSC_mattypes, # CSC_mattypes,
121 | nb.float64[:],
122 | nb.float64[:],
123 | nb.float64[:]), nopython=True)
124 | def Q_rmatvec(A, b, c, u):
125 | return -Q_matvec(A, b, c, u)
126 |
127 |
128 | #@nb.jit(nopython=True)
129 | @nb.jit(nb.float64[:](
130 | nb.float64[:],
131 | nb.float64[:],
132 | CSC_mattypes, # CSC_mattypes,
133 | nb.float64[:],
134 | nb.float64[:],
135 | cache_types), nopython=True)
136 | def residual_D(z, dz, A, b, c, cones_caches):
137 | m, n = len(b), len(c)
138 | zero, l, q, q_cache, s, s_cache_eivec, \
139 | s_cache_eival, ep, \
140 | ep_cache, ed, ed_cache = cones_caches
141 | du = embedded_cone_D(z, dz, zero, l, q, q_cache, s, s_cache_eivec,
142 | s_cache_eival, ep,
143 | ep_cache, ed, ed_cache, n)
144 | dv = du - dz
145 | return Q_matvec(A, b, c, du) - dv
146 |
147 |
148 | #@nb.jit(nopython=True)
149 | @nb.jit(nb.float64[:](
150 | nb.float64[:],
151 | nb.float64[:],
152 | CSC_mattypes, # CSC_mattypes,
153 | nb.float64[:],
154 | nb.float64[:],
155 | cache_types), nopython=True)
156 | def residual_DT(z, dres, A, b, c, cones_caches):
157 | m, n = len(b), len(c)
158 | zero, l, q, q_cache, s, s_cache_eivec, \
159 | s_cache_eival, ep, \
160 | ep_cache, ed, ed_cache = cones_caches
161 | return embedded_cone_D(z, -Q_matvec(A, b, c, dres) - dres,
162 | zero, l, q, q_cache, s, s_cache_eivec,
163 | s_cache_eival, ep,
164 | ep_cache, ed, ed_cache, n) + dres
165 |
166 |
167 | #@nb.jit(nopython=True)
168 | @nb.jit(nb.types.Tuple((nb.float64[:], nb.float64[:], nb.float64[:]))(
169 | nb.float64[:],
170 | CSC_mattypes, # CSC_mattypes,
171 | nb.float64[:],
172 | nb.float64[:],
173 | cache_types), nopython=True)
174 | def residual_and_uv(z, A, b, c, cones_caches):
175 | m, n = len(b), len(c)
176 | zero, l, q, q_cache, s, s_cache_eivec, \
177 | s_cache_eival, ep, \
178 | ep_cache, ed, ed_cache = cones_caches
179 | u = embedded_cone_Pi(z, zero, l, q, q_cache, s, s_cache_eivec,
180 | s_cache_eival, ep,
181 | ep_cache, ed, ed_cache, n)
182 | v = u - z
183 | return Q_matvec(A, b, c, u) - v, u, v
184 |
185 |
186 | def residual(z, A, b, c, cones_caches):
187 | A = sp.csc_matrix(A)
188 | A = (A.indptr, A.indices, A.data)
189 | res, u, v = residual_and_uv(z, A, b, c, cones_caches)
190 | return res
191 |
192 |
193 | # @nb.jit()
194 | # def print_header(): # z, norm_Q):
195 | # print()
196 | # print()
197 | # print('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
198 | # print(' Conic Solution Refinement ')
199 | # print()
200 | # print('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
201 | # print("ite. || N(z) ||_2 z[-1] LSQR time")
202 | # print('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
203 |
204 |
205 | # # @nb.jit()
206 | # # def subopt_stats(A, b, c, x, s, y):
207 | # # pri_res_norm = np.linalg.norm(
208 | # # A@x + s - b) / (1. + np.linalg.norm(b))
209 | # # dua_res_norm = np.linalg.norm(A.T@y + c) / \
210 | # # (1. + np.linalg.norm(c))
211 | # # rel_gap = np.abs(c@x + b@y) / \
212 | # # (1. + np.abs(c@x) + np.abs(b@y))
213 | # # compl_gap = s@y / (norm(s) * norm(y))
214 | # # return pri_res_norm, dua_res_norm, rel_gap, compl_gap
215 |
216 |
217 | # # def print_stats(i, residual, residual_DT, num_lsqr_iters, start_time):
218 | # @nb.jit()
219 | # def print_stats(i, residual, z, num_lsqr_iters, start_time):
220 | # print('%d\t%.2e\t%.0e\t%d\t%.2f' %
221 | # (i, np.linalg.norm(residual / z[-1]), z[-1],
222 | # num_lsqr_iters,
223 | # time.time() - start_time))
224 |
225 |
226 | # @nb.jit()
227 | # def print_footer(message):
228 | # print('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
229 | # print(message)
230 | # print('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
231 |
232 |
233 | @nb.jit(nb.float64[:](
234 | nb.float64[:],
235 | nb.float64[:],
236 | CSC_mattypes, # CSC_mattypes,
237 | nb.float64[:],
238 | nb.float64[:],
239 | cache_types,
240 | nb.float64[:]), nopython=True)
241 | def lsqr_D(z, dz, A, b, c, cache, residual):
242 | return residual_D(z, dz, A, b, c, cache) / np.abs(z[-1]) \
243 | - np.sign(z[-1]) * (residual / z[-1]**2) * dz[-1]
244 |
245 |
246 | @nb.jit(nb.float64[:](
247 | nb.float64[:],
248 | nb.float64[:],
249 | CSC_mattypes, # CSC_mattypes,
250 | nb.float64[:],
251 | nb.float64[:],
252 | cache_types,
253 | nb.float64[:]), nopython=True)
254 | def lsqr_DT(z, dres, A, b, c, cache, residual):
255 | m, n = len(b), len(c)
256 | e_minus1 = np.zeros(n + m + 1)
257 | e_minus1[-1] = 1.
258 | return residual_DT(z, dres, A, b, c, cache) / np.abs(z[-1]) \
259 | - np.sign(z[-1]) * (dres@residual / z[-1]**2) * e_minus1
260 |
261 |
262 | # def lsqr_D(z, dz, A, b, c, cache, residual):
263 | # return residual_D(z, dz, A, b, c, cache)
264 |
265 |
266 | # def lsqr_DT(z, dres, A, b, c, cache, residual):
267 | # return residual_DT(z, dres, A, b, c, cache)
268 |
269 |
270 | # def normalized_resnorm(residual, z):
271 | # return np.linalg.norm(residual) / np.abs(z[-1])
272 |
273 |
274 | # def refine(A, b, c, dim_dict, z,
275 | # iters=2,
276 | # lsqr_iters=30,
277 | # verbose=True):
278 |
279 | # m, n = A.shape
280 |
281 | # tmp = sp.csc_matrix(A)
282 | # A = (tmp.indptr, tmp.indices, tmp.data)
283 |
284 | # A_tr = sp.csc_matrix(tmp.T)
285 | # A_tr = (A_tr.indptr, A_tr.indices, A_tr.data)
286 |
287 | # start_time = time.time()
288 |
289 | # if verbose:
290 | # print_header()
291 |
292 | # cones_caches = make_prod_cone_cache(dim_dict)
293 | # residual, u, v = residual_and_uv(z, A, A_tr, b, c, cones_caches)
294 | # normres = np.linalg.norm(residual) / np.abs(z[-1])
295 |
296 | # refined_normres = float(normres)
297 | # refined = np.copy(z)
298 |
299 | # if verbose:
300 | # print_stats(0, residual, z, 0, start_time)
301 |
302 | # for i in range(iters):
303 |
304 | # if norm(lsqr_DT(z, residual, A, A_tr, b, c, cones_caches, residual)) == 0.:
305 | # if verbose:
306 | # print_footer('Residual orthogonal to derivative.')
307 | # return refined
308 |
309 | # # D = LinearOperator((m + n + 1, m + n + 1),
310 | # # matvec=lambda dz: lsqr_D(
311 | # # z, dz, A, A_tr, b, c, cones_caches, residual),
312 | # # rmatvec=lambda dres: lsqr_DT(
313 | # # z, dres, A, A_tr, b, c, cones_caches, residual)
314 | # # )
315 | # _ = lsqr(m + n + 1, m + n + 1,
316 | # (z, A, A_tr, b, c, cones_caches, residual),
317 | # # lambda dz: lsqr_D(z, dz, A, A_tr, b, c,
318 | # # cones_caches, residual),
319 | # # lambda dres: lsqr_DT(z, dres, A, A_tr, b,
320 | # # c, cones_caches, residual),
321 | # residual / np.abs(z[-1]), iter_lim=lsqr_iters)
322 | # step, num_lsqr_iters = _[0], _[2]
323 | # assert not True in np.isnan(step)
324 |
325 | # z = z - step
326 | # residual, u, v = residual_and_uv(
327 | # z, A, A_tr, b, c, cones_caches)
328 | # normres = np.linalg.norm(residual) / np.abs(z[-1])
329 |
330 | # if normres < refined_normres:
331 | # refined_normres = normres
332 | # refined = z / np.abs(z[-1])
333 |
334 | # if verbose:
335 | # print_stats(i + 1, np.copy(residual), np.copy(z),
336 | # num_lsqr_iters, start_time)
337 |
338 | # if i == iters - 1:
339 | # if verbose:
340 | # print_footer('Max num. refinement iters reached.')
341 |
342 | # return refined # z / np.abs(z[-1])
343 |
344 |
345 | # # def refine(A, b, c, dim_dict, z,
346 | # iters=2,
347 | # lsqr_iters=30,
348 | # max_backtrack=10,
349 | # verbose=True):
350 |
351 | # z = np.copy(z) # / np.abs(z[-1])
352 | # m, n = A.shape
353 |
354 | # start_time = time.time()
355 |
356 | # if verbose:
357 | # print_header() # z, sp.linalg.svds(Q(A, b, c), k=1)[1][0])
358 |
359 | # cones_caches = make_prod_cone_cache(dim_dict)
360 | # residual, u, v = residual_and_uv(z, A, b, c, cones_caches)
361 | # normres = np.linalg.norm(residual) / np.abs(z[-1])
362 |
363 | # if verbose:
364 | # print_stats(0, residual, z, 0, 0, start_time)
365 |
366 | # mu = np.zeros(len(residual))
367 | # theta = np.zeros(len(residual))
368 | # z_s = np.zeros((iters, len(residual)))
369 | # steps = np.zeros((iters, len(residual)))
370 | # residuals = np.zeros((iters, len(residual)))
371 |
372 | # for i in range(iters):
373 |
374 | # z_s[i] = z
375 | # residuals[i] = residual / np.abs(z[-1])
376 |
377 | # if norm(lsqr_DT(z, residual, A, b, c, cones_caches, residual)) == 0.:
378 | # if verbose:
379 | # print_footer('Residual orthogonal to derivative.')
380 | # return z / np.abs(z[-1])
381 |
382 | # # residual, u, v = residual_and_uv(
383 | # # z, A, b, c, cones_caches)
384 |
385 | # D = LinearOperator((m + n + 1, m + n + 1),
386 | # matvec=lambda dz: lsqr_D(
387 | # z, dz, A, b, c, cones_caches, residual),
388 | # rmatvec=lambda dres: lsqr_DT(
389 | # z, dres, A, b, c, cones_caches, residual)
390 | # )
391 | # _ = lsqr(D, residual / np.abs(z[-1]), # + mu / 2,
392 | # damp=0., # 1E-8,
393 | # atol=0.,
394 | # btol=0.,
395 | # show=False,
396 | # iter_lim=lsqr_iters)
397 | # step, num_lsqr_iters = _[0], _[2]
398 | # assert not True in np.isnan(step)
399 |
400 | # steps[i] = - step
401 |
402 | # # def backtrack():
403 | # # test_cone_cache = make_prod_cone_cache(dim_dict)
404 | # # for j in range(max_backtrack):
405 | # # test_z = z - step * 2**(-j)
406 | # # test_z /= np.abs(test_z[-1])
407 | # # test_res, u, v = residual_and_uv(
408 | # # test_z, A, b, c, test_cone_cache)
409 | # # test_normres = np.linalg.norm(test_res) # / np.abs(test_z[-1])
410 | # # if test_normres < normres:
411 | # # return test_z, test_res, test_cone_cache, test_normres, j, True
412 | # # return z, residual, cones_caches, normres, j, False
413 |
414 | # #z, residual, cones_cache, normres, num_btrk, improved = backtrack()
415 |
416 | # # theta_t_minus_one = theta_t
417 | # # theta_t = z - step
418 | # # theta_t /= np.abs(theta_t[-1])
419 | # # z = theta_t + .2 * (theta_t - theta_t_minus_one)
420 |
421 | # # ANDERSON = 20
422 |
423 | # # def anderson():
424 | # # import cvxpy as cvx
425 | # # w = cvx.Variable(ANDERSON)
426 | # # cvx.Problem(cvx.Minimize(cvx.norm(w @ residuals[i - ANDERSON + 1:i + 1])),
427 | # # [cvx.sum(w) == 1.]).solve()
428 | # # print(w.value)
429 | # # return w.value @ (z_s[i - ANDERSON + 1:i + 1] + steps[i - ANDERSON +
430 | # # 1:i + 1])
431 |
432 | # # z = anderson() if i > ANDERSON else (z - step)
433 | # z = z - step
434 | # # theta_t_minus_one = theta_t
435 | # # theta_t = z - step
436 | # # z = theta_t + .1 * (theta_t - theta_t_minus_one)
437 |
438 | # # theta = .5 * theta + step
439 | # # z = z - theta
440 |
441 | # #z /= np.abs(z[-1])
442 | # residual, u, v = residual_and_uv(z, A, b, c, cones_caches)
443 | # normres = np.linalg.norm(residual) / np.abs(z[-1])
444 | # num_btrk = 0
445 |
446 | # #mu += (1 / np.e) * residual
447 |
448 | # # if not improved:
449 | # # if verbose:
450 | # # print_footer('Hit maximum number of backtracks.')
451 | # # return z / np.abs(z[-1])
452 |
453 | # if verbose:
454 | # print_stats(i + 1, np.copy(residual), np.copy(z),
455 | # num_lsqr_iters, num_btrk, start_time)
456 |
457 | # if i == iters - 1:
458 | # if verbose:
459 | # print_footer('Max num. refinement iters reached.')
460 |
461 | # return z / np.abs(z[-1]) # , z_s, residuals
462 |
--------------------------------------------------------------------------------
/cpr/refine.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright 2019 Enzo Busseti, Walaa Moursi, and Stephen Boyd
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | """
16 |
17 | import time
18 | import numba as nb
19 | import numpy as np
20 | import scipy.sparse as sp
21 | from .cones import *
22 | from .lsqr import lsqr
23 |
24 | from .problem import residual_and_uv
25 |
26 |
27 | #@nb.jit((nb.types.float64[:], ))
28 | def print_header(entries_of_A, b, c, dim_dict): # z, norm_Q):
29 | n = len(b) + len(c) + 1
30 | print()
31 | print()
32 | print('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
33 | print(' CPR - Cone Program Refinement ')
34 | print('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
35 | print()
36 | print("refining the approximate solution z ∈ 𝗥^(%d)" % n)
37 | print('of a primal-dual homogeneously embedded problem with')
38 | # , ‖Q‖_F = %.3e,' %
39 | print('matrix Q ∈ 𝗥^(%d × %d), nnz(Q) = %d' %
40 | (n, n, 2 * (len(entries_of_A) + n - 1)))
41 | # np.sqrt(sum(entries_of_A**2) +
42 | # sum(b**2) + sum(c)**2)))
43 | # cone_string = 'and cone 𝒦 ∈ 𝗥^%d,\n' % n
44 | print('and cone 𝒦 product of')
45 | print('• zero cone 0^(%d)' %
46 | (len(c) + (dim_dict['z'] if 'z' in dim_dict else 0)))
47 | print('• non-negative cone 𝗥_+^(%d)' % (1 +
48 | (dim_dict['l'] if 'l' in dim_dict else 0)))
49 | if 'q' in dim_dict:
50 | print('• %d second-order cone(s) of total size %d' % (len(dim_dict['q']),
51 | sum(dim_dict['q'])))
52 | if 's' in dim_dict:
53 | print('• %d semidefinite cone(s) of total size %d' % (len(dim_dict['s']),
54 | sum(dim_dict['s'])))
55 | if 'ep' in dim_dict:
56 | print('• %d exponential cone(s)' % dim_dict['ep'])
57 | if 'ed' in dim_dict:
58 | print('• %d exponential dual cone(s)' % dim_dict['ed'])
59 | print()
60 | print('if z represents a solution (of the parent problem),')
61 | print('𝒩 (z) ∈ 𝗥^(%d) is the concatenation of the' % n)
62 | print('primal and dual errors, and the duality gap')
63 | print()
64 | print('if z represents a certificate of infeasibility or')
65 | print('unboundedness, 𝒩 (z) ∈ 𝗥^(%d) is the error of its' % n)
66 | print('linear system, and concatenated zeros')
67 | # if 'ep' in dim_dict or 'ed' in dim_dict:
68 | # print('each 𝒦_exp ∈ 𝗥^3 is an exponential cone')
69 | print()
70 | print('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
71 | print("ite. ‖𝒩 (z)‖ btrks SOL/CERT LSQR it. time")
72 | print('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
73 |
74 |
75 | # @nb.jit()
76 | # def subopt_stats(A, b, c, x, s, y):
77 | # pri_res_norm = np.linalg.norm(
78 | # A@x + s - b) / (1. + np.linalg.norm(b))
79 | # dua_res_norm = np.linalg.norm(A.T@y + c) / \
80 | # (1. + np.linalg.norm(c))
81 | # rel_gap = np.abs(c@x + b@y) / \
82 | # (1. + np.abs(c@x) + np.abs(b@y))
83 | # compl_gap = s@y / (norm(s) * norm(y))
84 | # return pri_res_norm, dua_res_norm, rel_gap, compl_gap
85 |
86 |
87 | # def print_stats(i, residual, residual_DT, num_lsqr_iters, start_time):
88 | @nb.jit()
89 | def print_stats(i, residual, btrks, z, num_lsqr_iters, start_time):
90 | print('%d\t%.2e\t%d\t%s\t%d\t%.2f' %
91 | (i, np.linalg.norm(residual / z[-1]), btrks,
92 | 'SOL' if z[-1] > 0 else 'CERT',
93 | num_lsqr_iters,
94 | time.time() - start_time))
95 |
96 |
97 | # @nb.jit()
98 | # def print_stats_ADMM(i, residual, u, z, num_lsqr_iters, start_time):
99 | # print('%d\t%.2e\t%.2e\t%.0e\t\t%d\t%.2f' %
100 | # (i, np.linalg.norm(residual / z[-1]),
101 | # np.linalg.norm(u),
102 | # z[-1],
103 | # num_lsqr_iters,
104 | # time.time() - start_time))
105 |
106 |
107 | @nb.jit()
108 | def print_footer(message):
109 | print('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
110 | print(message)
111 | print('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
112 |
113 |
114 | #@nb.jit(nopython=True)
115 | def refine(A, b, c, dim_dict, z,
116 | iters=2,
117 | lsqr_iters=30,
118 | verbose=True):
119 |
120 | z = np.copy(z)
121 |
122 | m, n = A.shape
123 |
124 | A = sp.csc_matrix(A)
125 | A = (A.indptr, A.indices, A.data)
126 |
127 | # A_tr = sp.csc_matrix(tmp.T)
128 | # A_tr = (A_tr.indptr, A_tr.indices, A_tr.data)
129 |
130 | start_time = time.time()
131 |
132 | if verbose:
133 | print_header(A[2], b, c, dim_dict)
134 |
135 | cones_caches = make_prod_cone_cache(dim_dict)
136 | residual, u, v = residual_and_uv(z, A, b, c, cones_caches)
137 | normres = np.linalg.norm(residual) / np.abs(z[-1])
138 |
139 | refined_normres = float(normres)
140 | refined = np.copy(z)
141 |
142 | if verbose:
143 | print_stats(0, residual, -1, z, 0, start_time)
144 |
145 | for i in range(iters):
146 |
147 | # if norm(lsqr_DT(z, residual, A, A_tr, b, c, cones_caches, residual)) == 0.:
148 | # if verbose:
149 | # print_footer('Residual orthogonal to derivative.')
150 | # return refined
151 |
152 | # D = LinearOperator((m + n + 1, m + n + 1),
153 | # matvec=lambda dz: lsqr_D(
154 | # z, dz, A, A_tr, b, c, cones_caches, residual),
155 | # rmatvec=lambda dres: lsqr_DT(
156 | # z, dres, A, A_tr, b, c, cones_caches, residual)
157 | # )
158 |
159 | # lambda dz: lsqr_D(z, dz, A, A_tr, b, c,
160 | # cones_caches, residual),
161 | # lambda dres: lsqr_DT(z, dres, A, A_tr, b,
162 | # c, cones_caches, residual),
163 |
164 | step, num_lsqr_iters, r1norm, acond = lsqr(m=m + n + 1,
165 | n=m + n + 1,
166 | operator_vars=(
167 | z, A, b, c, cones_caches, residual),
168 | b=residual / np.abs(z[-1]),
169 | iter_lim=lsqr_iters)
170 | # step, num_lsqr_iters = _[0], _[2]
171 | assert not True in np.isnan(step)
172 |
173 | old_normres = normres
174 |
175 | # backtrack
176 | for j in range(10):
177 | new_z = z - 2**(-j) * step
178 | residual, u, v = residual_and_uv(
179 | new_z, A, b, c, cones_caches)
180 | normres = np.linalg.norm(residual) / np.abs(new_z[-1])
181 | if normres < old_normres:
182 | z[:] = new_z
183 | # print('backtrack', j)
184 | if verbose:
185 | print_stats(i + 1, np.copy(residual), j, np.copy(z),
186 | num_lsqr_iters, start_time)
187 | break
188 |
189 | # z = z - step
190 | # residual, u, v = residual_and_uv(
191 | # z, A, b, c, cones_caches)
192 | # normres = np.linalg.norm(residual) / np.abs(z[-1])
193 |
194 | if normres < refined_normres:
195 | refined_normres = normres
196 | refined = z / np.abs(z[-1])
197 |
198 | # if verbose:
199 | # print_stats(i + 1, np.copy(residual), r1norm, np.copy(z),
200 | # num_lsqr_iters, start_time)
201 |
202 | if i == iters - 1:
203 | if verbose:
204 | print_footer('Max num. refinement iters reached.')
205 |
206 | return refined # z / np.abs(z[-1])
207 |
208 |
209 | # def ADMM_refine(A, b, c, dim_dict, z,
210 | # iters=2,
211 | # lsqr_iters=30,
212 | # verbose=True,
213 | # U_UPDATE=20):
214 |
215 | # m, n = A.shape
216 |
217 | # A = sp.csc_matrix(A)
218 | # A = (A.indptr, A.indices, A.data)
219 |
220 | # start_time = time.time()
221 |
222 | # if verbose:
223 | # print_header(A[2], b, c, dim_dict)
224 |
225 | # cones_caches = make_prod_cone_cache(dim_dict)
226 | # residual, u, v = residual_and_uv(z, A, b, c, cones_caches)
227 | # normres = np.linalg.norm(residual) / np.abs(z[-1])
228 |
229 | # refined_normres = float(normres)
230 | # refined = np.copy(z)
231 |
232 | # if verbose:
233 | # print_stats(0, residual, z, 0, start_time)
234 |
235 | # my_u = np.zeros(len(z))
236 |
237 | # for i in range(iters):
238 |
239 | # step, num_lsqr_iters, r1norm, acond = lsqr(m=m + n + 1,
240 | # n=m + n + 1,
241 | # operator_vars=(
242 | # z, A, b, c, cones_caches, residual),
243 | # b=(residual /
244 | # np.abs(z[-1])) + my_u,
245 | # iter_lim=lsqr_iters)
246 | # assert not True in np.isnan(step)
247 |
248 | # z = z - step
249 | # residual, u, v = residual_and_uv(
250 | # z, A, b, c, cones_caches)
251 | # normres = np.linalg.norm(residual) / np.abs(z[-1])
252 | # normalized_residual = residual / np.abs(z[-1])
253 |
254 | # if i % U_UPDATE == (U_UPDATE - 1):
255 | # my_u += normalized_residual
256 |
257 | # # print('norm mu:', np.linalg.norm(mu))
258 | # # print('normres:', normres)
259 | # # print('refined_normres:', refined_normres)
260 |
261 | # if normres < refined_normres:
262 | # refined_normres = normres
263 | # refined = z / np.abs(z[-1])
264 |
265 | # if verbose:
266 | # print_stats_ADMM(i + 1, np.copy(residual), np.copy(my_u), np.copy(z),
267 | # num_lsqr_iters, start_time)
268 |
269 | # if i == iters - 1:
270 | # if verbose:
271 | # print_footer('Max num. refinement iters reached.')
272 |
273 | # return refined # z / np.abs(z[-1])
274 |
--------------------------------------------------------------------------------
/cpr/solvers.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright 2019 Enzo Busseti, Walaa Moursi, and Stephen Boyd
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | """
16 |
17 | #__all__ = ['solve']
18 |
19 | import numpy as np
20 | import scs
21 | import ecos
22 | import time
23 |
24 | from .problem import *
25 | from .refine import *
26 |
27 |
28 | class SolverError(Exception):
29 | pass
30 |
31 |
32 | def scs_solve(A, b, c, dim_dict, init_z=None, **kwargs):
33 | """Wraps scs.solve for convenience."""
34 | scs_cones = {'l': dim_dict['l'] if 'l' in dim_dict else 0,
35 | 'q': dim_dict['q'] if 'q' in dim_dict else [],
36 | 's': dim_dict['s'] if 's' in dim_dict else [],
37 | 'ep': dim_dict['ep'] if 'ep' in dim_dict else 0,
38 | 'ed': dim_dict['ed'] if 'ed' in dim_dict else 0,
39 | 'f': dim_dict['z'] if 'z' in dim_dict else 0}
40 | #print('scs_cones', scs_cones)
41 | sol = scs.solve({'A': A, 'b': b,
42 | 'c': c},
43 | cone=scs_cones,
44 | **kwargs)
45 | info = sol['info']
46 |
47 | if info['statusVal'] > 0:
48 | z = xsy2z(sol['x'], sol['s'], sol['y'], tau=1., kappa=0.)
49 |
50 | if info['statusVal'] < 0:
51 | x = np.zeros_like(sol['x']) \
52 | if np.any(np.isnan(sol['x'])) else sol['x']
53 |
54 | s = np.zeros_like(sol['s']) \
55 | if np.any(np.isnan(sol['s'])) else sol['s']
56 |
57 | y = np.zeros_like(sol['y']) \
58 | if np.any(np.isnan(sol['y'])) else sol['y']
59 |
60 | if np.allclose(y, 0.) and c@x < 0:
61 | obj = c@x
62 | # assert obj < 0
63 | x /= -obj
64 | s /= -obj
65 | # print('primal res:', np.linalg.norm(A@x + s))
66 |
67 | if np.allclose(s, 0.) and b@y < 0:
68 | obj = b@y
69 | # assert obj < 0
70 | y /= -obj
71 | # print('dual res:', np.linalg.norm(A.T@y))
72 |
73 | # print('SCS NONSOLVED')
74 | # print('x', x)
75 | # print('s', s)
76 | # print('y', y)
77 |
78 | z = xsy2z(x, s, y, tau=0., kappa=1.)
79 |
80 | return z, info
81 |
82 |
83 | def ecos_solve(A, b, c, dim_dict, **kwargs):
84 | """Wraps ecos.solve for convenience."""
85 |
86 | ###
87 | # ECOS uses a different definition of the exp cone,
88 | # with y and z switched. In the future I might wrap it
89 | # (i.e., switch rows of A and elements of b, and switch
90 | # elements of the solutions s and y) but for now
91 | # I'm not supporting exp cones in ecos.
92 | ###
93 |
94 | ecos_cones = {'l': dim_dict['l'] if 'l' in dim_dict else 0,
95 | 'q': dim_dict['q'] if 'q' in dim_dict else []} # ,
96 | # 'e': dim_dict['ep'] if 'ep' in dim_dict else 0}
97 | # print(ecos_cones)
98 | if ('ep' in dim_dict and dim_dict['ep'] > 0
99 | or 's' in dim_dict and len(dim_dict['s']) > 0):
100 | raise SolverError(
101 | 'Only zero, linear, and second order cones supported.')
102 | zero = 0 if 'z' not in dim_dict else dim_dict['z']
103 | ecos_A, ecos_G = A[:zero, :], A[zero:, :]
104 | ecos_b, ecos_h = b[:zero], b[zero:]
105 | sol = ecos.solve(c=c, G=ecos_G, h=ecos_h, dims=ecos_cones,
106 | A=ecos_A, b=ecos_b, **kwargs)
107 |
108 | solution = True
109 |
110 | x = sol['x']
111 | s = np.concatenate([np.zeros(zero), sol['s']])
112 | # not sure we can trust this
113 | # s = b - A@x
114 | y = np.concatenate([sol['y'], sol['z']])
115 |
116 | if sol['info']['exitFlag'] == 0: # check that things make sense
117 | print('prim abs res.', np.linalg.norm(A@x + s - b))
118 | print('dua abs res.', np.linalg.norm(A.T@y + c))
119 | print('s^T y', s@y)
120 |
121 | if sol['info']['exitFlag'] in [1, 11]: # infeas
122 | solution = False
123 | obj = b@y
124 | assert (obj < 0)
125 | y /= -obj
126 |
127 | print('primal infeas. cert residual norm', np.linalg.norm(A.T@y))
128 | #cones = dim2cones(dim_dict)
129 | proj = prod_cone.Pi(-y, *make_prod_cone_cache(dim_dict))
130 | print('primal infeas dist from cone', np.linalg.norm(proj))
131 | # if not (np.linalg.norm(proj) == 0.) and sol['info']['exitFlag'] == 1.:
132 | # raise SolverError
133 |
134 | x = np.zeros_like(x)
135 | s = np.zeros_like(s)
136 |
137 | if sol['info']['exitFlag'] in [2, 12]: # unbound
138 | solution = False
139 | obj = c@x
140 | assert (obj < 0)
141 | x /= -obj
142 | s /= -obj
143 |
144 | print('dual infeas. cert residual norm', np.linalg.norm(A@x + s))
145 | proj = prod_cone.Pi(s, *make_prod_cone_cache(dim_dict))
146 | print('dual infeas cert dist from cone', np.linalg.norm(s - proj))
147 | # if not (np.linalg.norm(s - proj) == 0.) and sol['info']['exitFlag'] == 2.:
148 | # raise SolverError
149 | y = np.zeros_like(y)
150 |
151 | # print('ECOS SOLUTION')
152 | # print('solution', solution)
153 | # print('x', x)
154 | # print('s', s)
155 | # print('y', y)
156 |
157 | z = xsy2z(x, s, y, tau=solution, kappa=not solution)
158 |
159 | return z, sol['info']
160 |
161 |
162 | def solve(A, b, c, dim_dict,
163 | solver='scs',
164 | solver_options={},
165 | refine_solver_time_ratio=1.,
166 | max_iters=10,
167 | verbose=False,
168 | max_lsqr_iters=20,
169 | return_z=False):
170 |
171 | solver_start = time.time()
172 | if solver == 'scs':
173 | z, info = scs_solve(A, b, c, dim_dict, **solver_options)
174 | elif solver == 'ecos':
175 | z, info = ecos_solve(A, b, c, dim_dict, **solver_options)
176 | else:
177 | raise Exception('The only supported solvers are ecos and scs')
178 |
179 | solver_time = time.time() - solver_start
180 | A = sp.csc_matrix(A)
181 | #A_tr = sp.csc_matrix(A.T)
182 | new_residual, u, v = residual_and_uv(
183 | z, (A.indptr, A.indices, A.data), b, c, make_prod_cone_cache(dim_dict))
184 | x, s, y, tau, kappa = uv2xsytaukappa(u, v, A.shape[1])
185 |
186 | pres = np.linalg.norm(A@x + s - b) / (1 + np.linalg.norm(b))
187 | dres = np.linalg.norm(A.T@y + c) / (1 + np.linalg.norm(c))
188 | gap = np.abs(c@x + b@y) / (1 + np.abs(c@x) + np.abs(b@y))
189 |
190 | print('pres %.2e, dres %.2e, gap %.2e' % (pres, dres, gap))
191 |
192 | z_plus = refine(A, b, c, dim_dict, z,
193 | verbose=verbose,
194 | iters=max_iters,
195 | lsqr_iters=max_lsqr_iters) # ,
196 | # max_runtime=solver_time * refine_solver_time_ratio)
197 |
198 | if return_z:
199 | return z_plus, info
200 | else:
201 | new_residual, u, v =\
202 | residual_and_uv(z_plus, (A.indptr, A.indices, A.data), b, c,
203 | make_prod_cone_cache(dim_dict))
204 | x, s, y, tau, kappa = uv2xsytaukappa(u, v, A.shape[1])
205 | pres = np.linalg.norm(A@x + s - b) / (1 + np.linalg.norm(b))
206 | dres = np.linalg.norm(A.T@y + c) / (1 + np.linalg.norm(c))
207 | gap = np.abs(c@x + b@y) / (1 + np.abs(c@x) + np.abs(b@y))
208 | print('pres %.2e, dres %.2e, gap %.2e' % (pres, dres, gap))
209 | return x, s, y, info
210 |
--------------------------------------------------------------------------------
/cpr/sparse_matvec.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright 2019 Enzo Busseti, Walaa Moursi, and Stephen Boyd
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | """
16 |
17 | import numpy as np
18 | import numba as nb
19 |
20 |
21 | @nb.jit(nb.float64[:](nb.int64, nb.int32[:], nb.int32[:], nb.float64[:], nb.float64[:]),
22 | nopython=True)
23 | def csc_matvec(m, col_pointers, row_indeces, mat_elements, vector):
24 | """Multiply (m,n) matrix by (n) vector. Matrix is in compressed sparse cols fmt."""
25 | result = np.zeros(m)
26 | n = len(col_pointers) - 1
27 | assert len(vector) == n
28 |
29 | for j in range(n):
30 | i_s = row_indeces[
31 | col_pointers[j]:col_pointers[j + 1]]
32 | elements = mat_elements[
33 | col_pointers[j]:col_pointers[j + 1]]
34 |
35 | for cur, i in enumerate(i_s):
36 | result[i] += elements[cur] * vector[j]
37 |
38 | return result
39 |
40 |
41 | @nb.jit(nb.float64[:](nb.int32[:], nb.int32[:], nb.float64[:], nb.float64[:]),
42 | nopython=True)
43 | def csr_matvec(row_pointers, col_indeces, mat_elements, vector):
44 | """Multiply (m,n) matrix by (n) vector. Matrix is in compressed sparse rows fmt."""
45 | m = len(row_pointers) - 1
46 | result = np.zeros(m)
47 |
48 | for i in range(m):
49 | js = col_indeces[row_pointers[i]:row_pointers[i + 1]]
50 | elements = mat_elements[row_pointers[i]:row_pointers[i + 1]]
51 | for cur, j in enumerate(js):
52 | result[i] += vector[j] * elements[cur]
53 |
54 | return result
55 |
--------------------------------------------------------------------------------
/cpr/utils.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright 2019 Enzo Busseti, Walaa Moursi, and Stephen Boyd
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | """
16 |
17 | import numpy as np
18 | import scipy.sparse as sp
19 |
20 | from .cones import *
21 | from .problem import *
22 |
23 |
24 | def generate_dim_dict(zero_num_min=10,
25 | zero_num_max=50,
26 | nonneg_num_min=20,
27 | nonneg_num_max=100,
28 | lorentz_num_min=20,
29 | lorentz_num_max=100,
30 | lorentz_size_min=5,
31 | lorentz_size_max=20,
32 | semidef_num_min=5,
33 | semidef_num_max=20,
34 | semidef_size_min=2,
35 | semidef_size_max=10,
36 | exp_num_min=2,
37 | exp_num_max=10,
38 | random_ecos=.25):
39 | result = {}
40 | result['z'] = int(np.random.uniform(zero_num_min,
41 | zero_num_max))
42 | result['l'] = int(np.random.uniform(nonneg_num_min,
43 | nonneg_num_max))
44 | num_q = int(np.random.uniform(lorentz_num_min,
45 | lorentz_num_max))
46 | result['q'] = [int(np.random.uniform(lorentz_size_min,
47 | lorentz_size_max))
48 | for i in range(num_q)]
49 | num_s = int(np.random.uniform(semidef_num_min,
50 | semidef_num_max))
51 | result['s'] = [int(np.random.uniform(semidef_size_min,
52 | semidef_size_max))
53 | for i in range(num_s)]
54 | result['ep'] = int(np.random.uniform(exp_num_min,
55 | exp_num_max))
56 |
57 | result['ed'] = int(np.random.uniform(exp_num_min,
58 | exp_num_max))
59 | if np.random.uniform() < random_ecos:
60 | result['s'] = []
61 | result['ep'] = 0
62 | result['ed'] = 0
63 | return result
64 |
65 |
66 | def generate_problem(dim_dict=None,
67 | density=None,
68 | mode=None, # TODO fix tests
69 | # nondiff_point=False,
70 | # random_scale_max=None,
71 | min_val_entries=-1,
72 | max_val_entries=1): # TODO drop option
73 | """Generate random problem with given cone and density."""
74 |
75 | if dim_dict is None:
76 | dim_dict = generate_dim_dict()
77 |
78 | if density is None:
79 | density = np.random.uniform(.2, .4)
80 |
81 | if mode is None:
82 | mode = np.random.choice(['solvable', 'infeasible', 'unbounded'])
83 |
84 | m = (dim_dict['l'] if 'l' in dim_dict else 0) + \
85 | (dim_dict['z'] if 'z' in dim_dict else 0) + \
86 | (sum(dim_dict['q']) if 'q' in dim_dict else 0) + \
87 | (sum([sizemat2sizevec(el) for el in dim_dict['s']]) if 's' in dim_dict else 0) + \
88 | (3 * dim_dict['ep'] if 'ep' in dim_dict else 0) + \
89 | (3 * dim_dict['ed'] if 'ed' in dim_dict else 0)
90 |
91 | n = int(np.random.uniform(1, m))
92 |
93 | # r = np.zeros(m) if nondiff_point else
94 | r = np.random.uniform(min_val_entries, max_val_entries,
95 | size=m) # np.random.randn(m)
96 |
97 | cache = make_prod_cone_cache(dim_dict)
98 | s = prod_cone.Pi(r, *cache)
99 | y = s - r
100 |
101 | A = sp.rand(m, n, density=density, format='csc')
102 | A.data = np.random.uniform(min_val_entries, max_val_entries, size=A.nnz)
103 | # np.random.randn(
104 | # A.nnz) * np.random.uniform(1., 1. + random_scale_max)
105 | # x = np.random.randn(n) * np.random.uniform(1., 1. + random_scale_max)
106 | A /= np.linalg.norm(A.data)
107 | #A *= m/n
108 |
109 | x = np.random.uniform(min_val_entries, max_val_entries, size=n)
110 |
111 | if mode == 'solvable':
112 | b = A@x + s
113 | c = -A.T@y
114 | return A, b, c, dim_dict, x, s, y
115 |
116 | if mode == 'unbounded':
117 | x[x == 0] += 1
118 | error = A@x + s
119 | sparsity = np.array(A.todense() != 0, dtype=int)
120 | for i in range(m):
121 | j = np.argmax(sparsity[i, :])
122 | A[i, j] -= error[i] / x[j]
123 | assert np.allclose(A@x + s, 0.)
124 | # A = A - np.outer(s + A@x, x) / np.linalg.norm(x)**2 # dense...
125 | # c = np.random.randn(n) * np.random.uniform(1., 1. + random_scale_max)
126 | c = - x / (x@x) # c / (c@x)
127 | assert np.allclose(c@x, -1)
128 | # np.random.randn(m)
129 | b = np.random.uniform(min_val_entries, max_val_entries, size=m)
130 | y *= 0. # same as cert of unbound
131 | return A, b, c, dim_dict, x, s, y
132 |
133 | if mode == 'infeasible':
134 | error = A.T@y
135 | sparsity = np.array(A.todense() != 0, dtype=int)
136 | # old_nnz = A.nnz
137 | # D = np.array(A.todense() != 0, dtype=int)
138 | # B = sp.csc_matrix(np.multiply((A.T@y) / (D.T@y), D))
139 | # A = A - B
140 | for j in range(n):
141 | i = np.argmax(sparsity[:, j] * y**2)
142 | A[i, j] -= error[j] / y[i]
143 | assert np.allclose(A.T@y, 0.)
144 | # assert old_nnz == A.nnz
145 | # correction = A.T@y / sum(y)
146 | # A = A - np.outer(y, A.T@y) / np.linalg.norm(y)**2 # dense...
147 | # b = np.random.randn(m) * np.random.uniform(1., 1. + random_scale_max)
148 | b = - y / (y@y) # - b / (b@y)
149 | assert np.allclose(b@y, -1)
150 | # np.random.randn(n)
151 | c = np.random.uniform(min_val_entries, max_val_entries, size=n)
152 | x *= 0. # same as cert of infeas
153 | s *= 0.
154 | return sp.csc_matrix(A), b, c, dim_dict, x, s, y
155 |
156 | else:
157 | raise Exception('Invalid mode.')
158 |
--------------------------------------------------------------------------------
/examples/comparison.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cvxgrp/cone_prog_refine/9294a34ea487260a95f1c4e4ca6a3ffd62eaf681/examples/comparison.png
--------------------------------------------------------------------------------
/examples/improvement.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cvxgrp/cone_prog_refine/9294a34ea487260a95f1c4e4ca6a3ffd62eaf681/examples/improvement.png
--------------------------------------------------------------------------------
/pip_upload.sh:
--------------------------------------------------------------------------------
1 | python3 setup.py bdist_wheel
2 | twine upload --skip-existing dist/*
3 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 |
3 | setup(
4 | name='cpr',
5 | version='0.1',
6 | description='Cone program refinement.',
7 | author='Enzo Busseti, Walaa Moursi, Stephen Boyd',
8 | packages=['cpr'],
9 | install_requires=['numpy>=1.15.1', 'scipy>=1.1.0', 'numba>=0.36.2']
10 | )
11 |
--------------------------------------------------------------------------------
/test/test_cones.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (C) Enzo Busseti 2017-2019.
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU General Public License as published by
6 | the Free Software Foundation, either version 3 of the License, or
7 | (at your option) any later version.
8 |
9 | This program is distributed in the hope that it will be useful,
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | GNU General Public License for more details.
13 |
14 | You should have received a copy of the GNU General Public License
15 | along with this program. If not, see .
16 | """
17 |
18 | import unittest
19 | import numpy as np
20 |
21 | from cpr import *
22 |
23 | HOW_MANY_DERIVATIVE_SAMPLES = 1000
24 | HOW_LONG_DERIVATIVE_TEST_STEP = 1e-5
25 |
26 |
27 | def size_vec(x):
28 | return 1 if isinstance(x, float) else len(x)
29 |
30 |
31 | class MiscTest(unittest.TestCase):
32 | """ Test functions in the misc class."""
33 |
34 | def test_mat2vec(self):
35 | self.assertTrue(np.alltrue(mat2vec(np.eye(2)) == [1, 0, 1]))
36 | self.assertTrue(np.alltrue(
37 | mat2vec(np.array([[1, -1.], [-1, 1]])) == [1, -np.sqrt(2), 1]))
38 | self.assertTrue(np.alltrue(
39 | mat2vec(np.array([[1, -1, 0.], [-1, 1, 0], [0, 0, 1]])) ==
40 | [1, -np.sqrt(2), 0, 1, 0, 1]))
41 |
42 | def test_vec2mat(self):
43 | self.assertTrue(np.alltrue(vec2mat(np.array([1, 0., 1])) == np.eye(2)))
44 | self.assertTrue(np.alltrue(
45 | vec2mat(np.array([1, -np.sqrt(2), 1.])) == np.array([[1., -1], [-1, 1]])))
46 | self.assertTrue(np.alltrue(
47 | vec2mat(np.array([1., -np.sqrt(2), 0, 1, 0, 1])) ==
48 | np.array([[1, -1, 0], [-1, 1, 0], [0, 0, 1]])))
49 |
50 |
51 | class BaseTestCone(unittest.TestCase):
52 |
53 | """Base class for cones tests."""
54 |
55 | sample_vecs = []
56 | sample_vec_proj = []
57 | sample_vecs_are_in = []
58 | sample_vecs_are_diff = []
59 |
60 | def make_cache(self, n):
61 | if not self.test_cone is semi_def_cone_single_cache:
62 | return np.empty(n)
63 | m = sizevec2sizemat(n)
64 | return (np.empty(m**2), np.empty(m))
65 |
66 | def test_contains(self):
67 | for x, isin in zip(self.sample_vecs, self.sample_vecs_are_in):
68 | cache = self.make_cache(len(x))
69 | res = self.test_cone.Pi(x, cache)
70 | Pix = res
71 | self.assertTrue(np.alltrue(Pix == x) == isin)
72 |
73 | def test_proj(self):
74 | for x, proj_x in zip(self.sample_vecs, self.sample_vec_proj):
75 | cache = self.make_cache(len(x))
76 | Pix = self.test_cone.Pi(x, cache)
77 | self.assertTrue(np.allclose(Pix, proj_x))
78 |
79 | def test_derivative_random(self):
80 | for x, isdiff in zip(self.sample_vecs,
81 | self.sample_vecs_are_diff):
82 |
83 | x = np.random.randn(len(x))
84 | cache = self.make_cache(len(x))
85 |
86 | proj_x = self.test_cone.Pi(x, cache)
87 |
88 | # if not isdiff:
89 | # pass
90 | # delta = np.random.randn(size_vec(x)) * 0.0001
91 | # self.assertRaises(NonDifferentiable,
92 | # self.test_cone.D(x, delta, cache))
93 |
94 | # else:
95 | for i in range(HOW_MANY_DERIVATIVE_SAMPLES):
96 |
97 | delta = np.random.randn(
98 | size_vec(x)) * HOW_LONG_DERIVATIVE_TEST_STEP
99 | # print('x + delta:', x + delta)
100 | new_cache = self.make_cache(len(x))
101 | proj_x_plus_delta = self.test_cone.Pi(x + delta, new_cache)
102 | # proj_x_plus_delta, new_cache = self.test_cone.Pi(x + delta)
103 | # print('x, delta, cache:', x, delta, cache)
104 | dproj_x = self.test_cone.D(x, delta, cache)
105 |
106 | if not np.allclose(proj_x + dproj_x, proj_x_plus_delta):
107 | print('x:', x)
108 | print('Pi x:', proj_x)
109 | print('delta:')
110 | print(delta)
111 | print('Pi (x + delta) - Pi(x):')
112 | print(proj_x_plus_delta - proj_x)
113 | print('DPi delta:')
114 | print(dproj_x)
115 |
116 | self.assertTrue(np.allclose(
117 | proj_x + dproj_x,
118 | proj_x_plus_delta))
119 |
120 | # self.assertTrue(np.allclose(
121 | # proj_x + deriv.T@delta,
122 | # proj_x_plus_delta))
123 |
124 |
125 | class TestNonNeg(BaseTestCone):
126 |
127 | test_cone = non_neg_cone_cached
128 | sample_vecs = [np.array(el, dtype=float) for el in
129 | [np.array([-1., 0., 1.]), [1.], [0.], [],
130 | [-1.], [-2, 2.], [1, 1], np.arange(1, 100), [-2, -1, 1, 2]]]
131 | sample_vec_proj = [np.array(el) for el in
132 | [np.array([0., 0., 1.]), [1.], [0.], [],
133 | [0.], [0., 2.], [1, 1], np.arange(1, 100), [0, 0, 1, 2]]]
134 | sample_vecs_are_in = [False, True, True, True,
135 | False, False, True, True, False]
136 | sample_vecs_are_diff = [False, True, False, True,
137 | True, True, True, True, True]
138 |
139 |
140 | class TestFree(BaseTestCone):
141 |
142 | test_cone = free_cone_cached
143 | sample_vecs = [np.array(el, dtype=float) for el in
144 | [np.array([-1., 0., 1.]), [1.], [0.], [],
145 | [-1.], [-2., 2.], [1, 1], np.arange(1, 100), [-2, -1, 1, 2]]]
146 | sample_vecs_proj = sample_vecs
147 | sample_vecs_are_in = [True] * len(sample_vecs)
148 | sample_vecs_are_diff = [True] * len(sample_vecs)
149 |
150 |
151 | class TestZero(BaseTestCone):
152 |
153 | test_cone = zero_cone_cached
154 | sample_vecs = [np.array(el, dtype=float) for el in
155 | [np.array([-1., 0., 1.]), [1.],
156 | [-1.], [-2., 2.], [1,
157 | 1], np.arange(1, 100), [-2, -1, 1, 2],
158 | [0.], [0., 0.], np.zeros(10), np.array([])]]
159 | sample_vec_proj = [np.array(el) for el in
160 | [np.array([0., 0., 0.]), [0.],
161 | [0.], [0., 0.], [0, 0], np.zeros(99), [0, 0, 0, 0],
162 | [0.], [0, 0], np.zeros(10), []]]
163 | sample_vecs_are_in = [False] * (len(sample_vecs) - 4) + [True] * 4
164 | sample_vecs_are_diff = [True] * len(sample_vecs)
165 |
166 |
167 | class TestExpPri(BaseTestCone):
168 |
169 | test_cone = exp_pri_cone
170 | sample_vecs = [np.array([0., 0., 0.]),
171 | np.array([-10., -10., -10.]),
172 | np.array([10., 10., 10.]),
173 | np.array([1., 2., 3.]),
174 | np.array([100., 2., 300.]),
175 | np.array([-1., -2., -3.]),
176 | np.array([-10., -10., 10.]),
177 | np.array([1., -1., 1.]),
178 | np.array([0.08755124, -1.22543552, 0.84436298])]
179 | sample_vec_proj = [np.array([0., 0., 0.]),
180 | np.array([-10., 0., 0.]),
181 | np.array([4.26306172, 7.51672777, 13.25366605]),
182 | np.array([0.8899428, 1.94041882, 3.06957225]),
183 | np.array([73.77502858, 33.51053837, 302.90131756]),
184 | np.array([-1., 0., 0.]),
185 | np.array([-10., 0., 10.]),
186 | np.array([0.22972088, 0.09487128, 1.06839895]),
187 | np.array([3.88378507e-06, 2.58963810e-07, 0.84436298])]
188 | sample_vecs_are_in = [True, False, False,
189 | False, False, False, False, False]
190 | sample_vecs_are_diff = [False, True, True, True, True, True, True, True]
191 |
192 |
193 | class TestExpDua(BaseTestCone):
194 |
195 | test_cone = exp_dua_cone
196 | sample_vecs = [np.array([0., 0., 0.]),
197 | np.array([-1., 1., 100.]),
198 | np.array([1., 1., 100.]),
199 | np.array([-1., -2., -3.])]
200 | sample_vec_proj = [np.array([0., 0., 0.]),
201 | np.array([-1., 1., 100.]),
202 | np.array([0., 1., 100.]),
203 | np.array([-0.1100572, -0.05958119, 0.06957226])]
204 | sample_vecs_are_in = [True, True, False, False]
205 | sample_vecs_are_diff = [False, True, True, True]
206 |
207 |
208 | class TestSecondOrder(BaseTestCone):
209 |
210 | test_cone = sec_ord_cone
211 | sample_vecs = [np.array([1., 0., 0.]),
212 | np.array([1., 2., 2.]),
213 | np.array([-10., 2., 2.]),
214 | np.array([-2 * np.sqrt(2), 2., 2.]),
215 | np.array([-1., 2., 2.]),
216 | np.array([0., 1.]),
217 | np.array([.5, -.5])]
218 | sample_vec_proj = [np.array([1., 0., 0.]),
219 | [(2 * np.sqrt(2) + 1) / (2),
220 | (2 * np.sqrt(2) + 1) / (2 * np.sqrt(2)),
221 | (2 * np.sqrt(2) + 1) / (2 * np.sqrt(2))],
222 | np.array([0, 0, 0]),
223 | np.array([0, 0, 0]),
224 | np.array([0.9142135623730951,
225 | 0.6464466094067263,
226 | 0.6464466094067263
227 | ]),
228 | np.array([.5, .5]),
229 | np.array([.5, -.5])]
230 | sample_vecs_are_in = [True, False, False, False, False, False, True]
231 | sample_vecs_are_diff = [True, True, True, False, True, True, False]
232 |
233 |
234 | class TestProduct(BaseTestCone):
235 |
236 | test_cone = prod_cone
237 |
238 | def test_baseProduct(self):
239 |
240 | cache = make_prod_cone_cache({'l': 3})
241 | Pix = prod_cone.Pi(np.arange(3.), *cache)
242 | self.assertTrue(np.alltrue(Pix == np.arange(3.)))
243 |
244 | cache = make_prod_cone_cache({'l': 3})
245 | Pix = prod_cone.Pi(np.array([1., -1., -1.]), *cache)
246 | self.assertTrue(np.alltrue(Pix == [1, 0, 0]))
247 |
248 | # cones = [[non_neg_cone, 2], [semi_def_cone, 1]]
249 | cache = make_prod_cone_cache({'l': 2, 's': [1]})
250 | Pix = prod_cone.Pi(np.arange(3.), *cache)
251 | self.assertTrue(np.alltrue(Pix == range(3)))
252 | Pix = prod_cone.Pi(np.array([1., -1., -1.]), *cache)
253 | self.assertTrue(np.alltrue(Pix == [1, 0, 0]))
254 |
255 | # cones = [[semi_def_cone, 3], [semi_def_cone, 1]]
256 | cache = make_prod_cone_cache({'s': [2, 1]})
257 | Pix = prod_cone.Pi(np.arange(4.), *cache)
258 | self.assertTrue(np.allclose(Pix - np.array(
259 | [0.20412415, 0.90824829, 2.02062073, 3]), 0.))
260 | Pix = prod_cone.Pi(np.array([1, -20., 1, -1]), *cache)
261 |
262 | self.assertTrue(np.allclose(Pix, np.array([7.57106781, -10.70710678,
263 | 7.57106781, 0.])))
264 |
265 | def test_deriv_Product(self):
266 |
267 | dim_dict = {'z': 2, 'l': 20, 'q': [3, 4],
268 | 's': [3, 4, 5], 'ep': 10, 'ed': 10}
269 | cache = make_prod_cone_cache(dim_dict)
270 | m = 22 + 7 + (6 + 10 + 15) + 30 + 30
271 | # samples = [np.array([-5.3, 2., 11]), np.random.randn(m)
272 | # np.array([-10.3, -22., 13.])]
273 |
274 | for j in range(100):
275 | x = np.random.randn(m)
276 | proj_x = prod_cone.Pi(x, *cache)
277 | for i in range(HOW_MANY_DERIVATIVE_SAMPLES):
278 | delta = np.random.randn(size_vec(x)) * \
279 | HOW_LONG_DERIVATIVE_TEST_STEP
280 | # print('x + delta:', x + delta)
281 | new_cache = make_prod_cone_cache(dim_dict)
282 | proj_x_plus_delta = prod_cone.Pi(x + delta, *new_cache)
283 |
284 | dproj_x = prod_cone.D(x, delta, *cache)
285 |
286 | if not np.allclose(proj_x + dproj_x, proj_x_plus_delta, atol=1e-6):
287 | print(dim_dict)
288 | print('x:', x)
289 | print('Pi (x):', proj_x)
290 | # print(delta)
291 | print('Pi (x + delta) - Pi(x):')
292 | print(proj_x_plus_delta - proj_x)
293 | print('DPi delta:')
294 | print(dproj_x)
295 |
296 | print('error:')
297 | print(proj_x + dproj_x -
298 | proj_x_plus_delta)
299 |
300 | self.assertTrue(np.allclose(
301 | proj_x + dproj_x,
302 | proj_x_plus_delta, atol=1e-6))
303 |
304 |
305 | class TestSemiDefinite(BaseTestCone):
306 |
307 | test_cone = semi_def_cone_single_cache
308 | sample_vecs = [np.array([2, 0, 0, 2, 0, 2.]), np.array([1.]), np.array([-1.]),
309 | np.array([10, 20., 10.]),
310 | np.array([10, 0., -3.]),
311 | np.array([10, 20., 0., 10, 0., 10]),
312 | np.array([1, 20., 30., 4, 50., 6]),
313 | np.array([1, 20., 30., 4, 50., 6, 200., 20., 1., 0.])]
314 | sample_vec_proj = [[2, 0, 0, 2, 0, 2], [1.], [0.],
315 | [[12.07106781, 17.07106781, 12.07106781]],
316 | np.array([10, 0., 0.]),
317 | np.array([12.07106781, 17.07106781,
318 | 0., 12.07106781, 0., 10.]),
319 | np.array([10.11931299, 19.85794691, 21.57712079,
320 | 19.48442822, 29.94069045,
321 | 23.00413782]),
322 | np.array([10.52224268, 13.74405405, 21.782617, 10.28175521,
323 | 99.29457317, 5.30953205, 117.32861549, 23.76075308,
324 | 2.54829623, 69.3944742])]
325 | sample_vecs_are_in = [True, True, False, False, False, False, False, False]
326 | sample_vecs_are_diff = [True, True, True, True, True, True, True, True]
327 |
328 |
329 | class TestEmbeddedCone(unittest.TestCase):
330 |
331 | def test_embedded_vars(self):
332 | dim_dict = {'z': 2, 'l': 3, 'q': [4], 's': [3]}
333 | A, b, c, _, x_true, s_true, y_true = generate_problem(dim_dict,
334 | mode='solvable')
335 | m, n = A.shape
336 | cone_caches = make_prod_cone_cache(dim_dict)
337 | # problem = ConicProblem(A, b, c, cones)
338 | u_true, v_true = xsy2uv(x_true, s_true, y_true, 1., 0.)
339 | x, s, y, _, _ = uv2xsytaukappa(u_true, v_true, len(x_true))
340 |
341 | self.assertTrue(np.alltrue(x == x_true))
342 | self.assertTrue(np.alltrue(s == s_true))
343 | self.assertTrue(np.alltrue(y == y_true))
344 |
345 | u, v = xsy2uv(x, s, y, 1., 0.)
346 |
347 | self.assertTrue(np.alltrue(u == u_true))
348 | self.assertTrue(np.alltrue(v == v_true))
349 |
350 | print('u', u)
351 | print('v', v)
352 | z = u - v
353 | print('z', z)
354 |
355 | proj_u = embedded_cone_Pi(z, *cone_caches, n)
356 | proj_v = proj_u - z
357 |
358 | print(' u = Pi z', proj_u)
359 | print('v = Pi z - z', proj_v)
360 | self.assertTrue(np.allclose(proj_u.T@proj_v, 0.))
361 | self.assertTrue(np.allclose(proj_u - u, 0.))
362 | self.assertTrue(np.allclose(proj_v - v, 0.))
363 | self.assertTrue(np.allclose(proj_u - proj_v, z))
364 |
365 | def test_embedded_cone_der_proj(self):
366 | for j in range(100):
367 | dim_dict = {'f': 2, 'l': 20, 'q': [2, 3, 5],
368 | 's': [3, 4, 5], 'ep': 10, 'ed': 10}
369 | A, b, c, _, x_true, s_true, y_true = generate_problem(
370 | dim_dict, mode='solvable')
371 | m, n = A.shape
372 | cone_caches = make_prod_cone_cache(dim_dict)
373 | # problem = ConicProblem(A, b, c, cones)
374 | u_true, v_true = xsy2uv(x_true, s_true, y_true, 1., 0.)
375 | z_true = u_true - v_true
376 |
377 | for i in range(HOW_MANY_DERIVATIVE_SAMPLES):
378 | delta = np.random.randn(len(z_true)) * \
379 | HOW_LONG_DERIVATIVE_TEST_STEP
380 | proj_u = embedded_cone_Pi(z_true, *cone_caches, n)
381 | proj_v = proj_u - z_true
382 |
383 | self.assertTrue(np.allclose(proj_u - u_true, 0.))
384 | self.assertTrue(np.allclose(proj_v - v_true, 0.))
385 | dproj = embedded_cone_D(z_true, delta, *cone_caches, n)
386 |
387 | # deriv = EmbeddedConeDerProj(problem.n, z_true, cone)
388 | new_cone_caches = make_prod_cone_cache(dim_dict)
389 | u_plus_delta = embedded_cone_Pi(
390 | z_true + delta, *new_cone_caches, n)
391 |
392 | # u_plus_delta, v_plus_delta = problem.embedded_cone_proj(z_true + delta)
393 | # dproj = deriv@delta
394 |
395 | error = u_true + dproj - u_plus_delta
396 | m, n = A.shape
397 | if not np.allclose(error, 0., atol=1e-6):
398 | print('z:', z_true)
399 | print('delta:')
400 | print(delta)
401 | print('Pi (z + delta) - Pi(z):')
402 | print(u_plus_delta - u_true)
403 | print('DPi delta:')
404 | print(dproj)
405 | print('error:')
406 | print(error[:n])
407 | print(error[n:-1])
408 | print(error[-1])
409 |
410 | self.assertTrue(np.allclose(
411 | error, 0., atol=1e-6))
412 |
413 |
414 | if __name__ == '__main__':
415 | unittest.main()
416 |
--------------------------------------------------------------------------------
/test/test_problem.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (C) Enzo Busseti 2017-2019.
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU General Public License as published by
6 | the Free Software Foundation, either version 3 of the License, or
7 | (at your option) any later version.
8 |
9 | This program is distributed in the hope that it will be useful,
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | GNU General Public License for more details.
13 |
14 | You should have received a copy of the GNU General Public License
15 | along with this program. If not, see .
16 | """
17 |
18 | import unittest
19 | import numpy as np
20 |
21 | from cpr import *
22 | import scipy.sparse as sp
23 |
24 | import time
25 |
26 |
27 | class ProblemTest(unittest.TestCase):
28 |
29 | def test_Q(self):
30 | dim_dict = {'f': 10, 'l': 20, 'q': [10], 's': [5], 'ep': 10, 'ed': 10}
31 | A, b, c, _, x_true, s_true, y_true = generate_problem(dim_dict,
32 | mode='solvable')
33 |
34 | # cones = dim2cones(dim_dict)
35 | cone_caches = make_prod_cone_cache(dim_dict)
36 | u, v = xsy2uv(x_true, s_true, y_true)
37 | res = residual(u - v, A, b, c, cone_caches)
38 |
39 | self.assertTrue(np.allclose(res[:-1], 0.))
40 | self.assertTrue(np.allclose(res[-1], 0.))
41 |
42 | # def test_embedded_vars(self):
43 | # dim_dict = {'f': 2, 'l': 3, 'q': [4], 's': [3]}
44 | # A, b, c, _, x_true, s_true, y_true = generate_problem(dim_dict,
45 | # mode='solvable')
46 | # m, n = A.shape
47 | # cone_caches = make_prod_cone_cache(dim_dict)
48 | # # problem = ConicProblem(A, b, c, cones)
49 | # u_true, v_true = xsy2uv(x_true, s_true, y_true, 1., 0.)
50 | # x, s, y, _, _ = uv2xsytaukappa(u_true, v_true, len(x_true))
51 |
52 | # self.assertTrue(np.alltrue(x == x_true))
53 | # self.assertTrue(np.alltrue(s == s_true))
54 | # self.assertTrue(np.alltrue(y == y_true))
55 |
56 | # u, v = xsy2uv(x, s, y, 1., 0.)
57 |
58 | # self.assertTrue(np.alltrue(u == u_true))
59 | # self.assertTrue(np.alltrue(v == v_true))
60 |
61 | # print('u', u)
62 | # print('v', v)
63 | # z = u - v
64 | # print('z', z)
65 |
66 | # proj_u = embedded_cone_Pi(z, cone_caches, n)
67 | # proj_v = proj_u - z
68 |
69 | # print(' u = Pi z', proj_u)
70 | # print('v = Pi z - z', proj_v)
71 | # self.assertTrue(np.allclose(proj_u.T@proj_v, 0.))
72 | # self.assertTrue(np.allclose(proj_u - u, 0.))
73 | # self.assertTrue(np.allclose(proj_v - v, 0.))
74 | # self.assertTrue(np.allclose(proj_u - proj_v, z))
75 |
76 | # def test_embedded_cone_der_proj(self):
77 | # dim_dict = {'f': 2, 'l': 20, 'q': [2, 3, 5], 's': [3, 4], 'ep': 4}
78 | # A, b, c, _, x_true, s_true, y_true = generate_problem(
79 | # dim_dict, mode='solvable')
80 | # m, n = A.shape
81 | # cone_caches = make_prod_cone_cache(dim_dict)
82 | # #problem = ConicProblem(A, b, c, cones)
83 | # u_true, v_true = xsy2uv(x_true, s_true, y_true, 1., 0.)
84 | # z_true = u_true - v_true
85 |
86 | # delta = np.random.randn(len(z_true)) * 1E-7
87 | # proj_u = embedded_cone_Pi(z_true, cone_caches, n)
88 | # proj_v = proj_u - z_true
89 |
90 | # self.assertTrue(np.allclose(proj_u - u_true, 0.))
91 | # self.assertTrue(np.allclose(proj_v - v_true, 0.))
92 | # dproj = embedded_cone_D(z_true, delta, cone_caches, n)
93 |
94 | # #deriv = EmbeddedConeDerProj(problem.n, z_true, cone)
95 | # new_cone_caches = make_prod_cone_cache(dim_dict)
96 | # u_plus_delta = embedded_cone_Pi(
97 | # z_true + delta, new_cone_caches, n)
98 |
99 | # #u_plus_delta, v_plus_delta = problem.embedded_cone_proj(z_true + delta)
100 | # # dproj = deriv@delta
101 |
102 | # print('delta:')
103 | # print(delta)
104 | # print('Pi (z + delta) - Pi(z):')
105 | # print(u_plus_delta - u_true)
106 | # print('DPi delta:')
107 | # print(dproj)
108 | # print('error:')
109 | # print(u_true + dproj - u_plus_delta)
110 |
111 | # self.assertTrue(np.allclose(
112 | # u_true + dproj,
113 | # u_plus_delta, atol=1E-6))
114 |
115 | def test_residual_der(self):
116 | dim_dict = {'l': 10, 'q': [5, 10], 's': [3, 4], 'ep': 10, 'ed': 2}
117 | A, b, c, _, x_true, s_true, y_true = generate_problem(
118 | dim_dict, mode='solvable', density=.3)
119 | m, n = A.shape
120 | cones_caches = make_prod_cone_cache(dim_dict)
121 | u_true, v_true = xsy2uv(x_true, s_true, y_true, 1., 0.)
122 | z_true = u_true - v_true
123 |
124 | res = residual(z_true, A, # A.T,
125 | b, c, cones_caches)
126 | delta = np.random.randn(len(z_true)) * 1E-7
127 | residual_z_plus_delta = residual(z_true + delta, A, # A.T,
128 | b, c,
129 | make_prod_cone_cache(dim_dict))
130 |
131 | A = sp.csc_matrix(A)
132 | # A_tr = sp.csc_matrix(A.T)
133 |
134 | (A.indptr, A.indices, A.data),
135 | #(A_tr.indptr, A_tr.indices, A_tr.data),
136 |
137 | dres = residual_D(z_true, delta, (A.indptr, A.indices, A.data),
138 | #(A_tr.indptr, A_tr.indices, A_tr.data),
139 | b, c, cones_caches)
140 |
141 | print('delta:')
142 | print(delta)
143 | print('Res (z + delta) - Res(z):')
144 | print(residual_z_plus_delta - res)
145 | print('dRes')
146 | print(dres)
147 |
148 | self.assertTrue(np.allclose(
149 | res + dres,
150 | residual_z_plus_delta, atol=1e-5))
151 |
152 | # print('testing DT')
153 | # res, cones_caches = residual(z_true, A, b, c, cones)
154 | # delta = np.random.randn(len(z_true)) * 1E-5
155 | # #residual_z_plus_delta, _ = residual(z_true + delta, A, b, c, cones)
156 | # dz = residual_DT(z_true, delta, A, b, c, cones_caches)
157 | # new_res, _ = residual(z_true + dz, A, b, c, cones)
158 |
159 | # print('delta:')
160 | # print(delta)
161 | # print('dz')
162 | # print(dz)
163 | # print('dRes')
164 | # print(new_res - res)
165 |
166 | # self.assertTrue(np.allclose(
167 | # new_res - res,
168 | # delta))
169 |
170 | def check_refine_ecos(self, dim_dict, **kwargs):
171 | solvable = True
172 | if not ('mode' in kwargs):
173 | kwargs['mode'] = 'solvable'
174 | if (kwargs['mode'] != 'solvable'):
175 | solvable = False
176 | print('generating problem')
177 | A, b, c, dim_dict, x_true, s_true, y_true = generate_problem(dim_dict,
178 | **kwargs)
179 | m, n = A.shape
180 |
181 | u, v = xsy2uv(x_true, s_true, y_true, solvable, not solvable)
182 |
183 | embedded_res = residual(u - v, A, b, c,
184 | make_prod_cone_cache(dim_dict))
185 | self.assertTrue(np.allclose(embedded_res, 0.))
186 |
187 | print('calling solver')
188 | solver_start = time.time()
189 | z, info = ecos_solve(A, b, c, dim_dict,
190 | verbose=True,
191 | feastol=1e-15,
192 | reltol=1e-15,
193 | abstol=1e-15,
194 | )
195 | solver_end = time.time()
196 | pridua_res = residual(z, A, b, c, make_prod_cone_cache(dim_dict))
197 | if not (np.alltrue(pridua_res == 0.)):
198 | refine_start = time.time()
199 | z_plus = refine(A, b, c, dim_dict, z)
200 | refine_end = time.time()
201 |
202 | pridua_res_new = residual(
203 | z_plus, A, b, c, make_prod_cone_cache(dim_dict))
204 | print('\n\nSolver time: %.2e' % (solver_end - solver_start))
205 | print("||pridua_res before refinement||")
206 | oldnorm = np.linalg.norm(pridua_res)
207 | print('%.6e' % oldnorm)
208 | print('\n\nRefinement time: %.2e' % (refine_end - refine_start))
209 | print("||pridua_res after refinement||")
210 | newnorm = np.linalg.norm(pridua_res_new)
211 | print('%.6e' % newnorm)
212 | self.assertTrue(newnorm <= oldnorm)
213 | if (newnorm == oldnorm):
214 | print('\n\n\nrefinement FAILED!!!, dims %s\n\n\n' % dim_dict)
215 | else:
216 | print('\n\n\nrefinement SUCCEDED!!!, dims %s\n\n\n' % dim_dict)
217 | print('new optval', c@z_plus[:n])
218 |
219 | def check_refine_scs(self, dim_dict, **kwargs):
220 | solvable = True
221 | if not ('mode' in kwargs):
222 | kwargs['mode'] = 'solvable'
223 | if (kwargs['mode'] != 'solvable'):
224 | solvable = False
225 | print('generating problem')
226 | A, b, c, _, x_true, s_true, y_true = generate_problem(
227 | dim_dict, **kwargs)
228 | m, n = A.shape
229 |
230 | u, v = xsy2uv(x_true, s_true, y_true, solvable, not solvable)
231 |
232 | embedded_res = residual(
233 | u - v, A, b, c, make_prod_cone_cache(dim_dict))
234 | self.assertTrue(np.allclose(embedded_res, 0.))
235 |
236 | print('calling solver')
237 | solver_start = time.time()
238 | z, info = scs_solve(A, b, c, dim_dict,
239 | verbose=True,
240 | eps=1e-15,
241 | max_iters=1000)
242 | solver_end = time.time()
243 | pridua_res = residual(z, A, b, c, make_prod_cone_cache(dim_dict))
244 | if not (np.alltrue(pridua_res == 0.)):
245 | refine_start = time.time()
246 | z_plus = refine(A, b, c, dim_dict, z)
247 | refine_end = time.time()
248 |
249 | pridua_res_new = residual(
250 | z_plus, A, b, c, make_prod_cone_cache(dim_dict))
251 | print('\n\nSolver time: %.2e' % (solver_end - solver_start))
252 | print("||pridua_res before refinement||")
253 | oldnorm = np.linalg.norm(pridua_res)
254 | print('%.6e' % oldnorm)
255 | print('\n\nRefinement time: %.2e' % (refine_end - refine_start))
256 | print("||pridua_res after refinement||")
257 | newnorm = np.linalg.norm(pridua_res_new)
258 | print('%.6e' % newnorm)
259 | self.assertTrue(newnorm <= oldnorm)
260 | if (newnorm == oldnorm):
261 | print('\n\n\nrefinement FAILED!!!, dims %s\n\n\n' % dim_dict)
262 | else:
263 | print('\n\n\nrefinement SUCCEDED!!!, dims %s\n\n\n' % dim_dict)
264 | print('new optval', c@z_plus[:n])
265 |
266 | def test_solve_and_refine(self):
267 | for dims in [{'l': 20, 'q': [10] * 5},
268 | {'l': 20, 's': [10] * 5},
269 | #{'l': 290, 'q': [10] * 10},
270 | #{'l': 1000},
271 | {'l': 50, 'q': [10] * 5, 's':[20] * 1}]:
272 | np.random.seed(1)
273 | A, b, c, _, x_true, s_true, y_true = generate_problem(
274 | dims, mode='solvable')
275 | m, n = A.shape
276 |
277 | self.assertTrue(np.allclose(A@x_true + s_true - b, 0))
278 | self.assertTrue(np.allclose(A.T@y_true + c, 0))
279 | self.assertTrue(np.allclose(b.T@y_true + c.T@x_true, 0))
280 |
281 | x, s, y, info = solve(A, b, c, dims,
282 | solver='scs',
283 | solver_options={ # 'max_iters': 500,
284 | 'eps': 1e-9,
285 | 'verbose': True},
286 | refine_solver_time_ratio=5,
287 | verbose=True)
288 | self.assertTrue(np.allclose(A@x + s - b, 0, atol=1e-5))
289 | self.assertTrue(np.allclose(A.T@y + c, 0, atol=1e-5))
290 | self.assertTrue(np.allclose(b.T@y + c.T@x, 0, atol=1e-5))
291 |
292 | def test_infeasible(self):
293 | self.check_refine_scs({'l': 20, 'q': [10] * 5}, mode='infeasible')
294 | self.check_refine_ecos({'l': 20, 'q': [10] * 5}, mode='infeasible')
295 | # self.check_refine_scs({'s': [20]}, mode='infeasible')
296 | self.check_refine_scs({'s': [10]}, mode='infeasible')
297 | self.check_refine_scs({'q': [50]}, mode='infeasible')
298 | self.check_refine_ecos({'q': [50]}, mode='infeasible')
299 |
300 | def test_unbound(self):
301 | self.check_refine_scs({'l': 20, 'q': [10] * 5}, mode='unbounded')
302 | self.check_refine_ecos({'l': 20, 'q': [10] * 5}, mode='unbounded')
303 | # self.check_refine_scs({'s': [20]}, mode='unbounded')
304 | self.check_refine_scs({'s': [10], 'q': [5]}, mode='unbounded')
305 | self.check_refine_scs({'q': [50, 5]}, mode='unbounded')
306 | self.check_refine_ecos({'q': [50, 5]}, mode='unbounded')
307 |
308 | # def test_nondiff(self):
309 | # self.check_refine_scs({'l': 20, 'q': [10] * 5}, nondiff_point=True)
310 | # # self.check_refine_scs({'s': [20]}, nondiff_point=True)
311 | # self.check_refine_scs({'s': [10]}, nondiff_point=True)
312 | # self.check_refine_scs({'q': [50]}, nondiff_point=True)
313 |
314 | def test_scs(self):
315 | self.check_refine_scs({'l': 20, 'q': [10] * 5})
316 | self.check_refine_scs({'l': 20, 'q': [10] * 5, 'ep': 20})
317 | self.check_refine_scs({'l': 20, 'q': [10] * 5, 'ed': 20})
318 |
319 | # self.check_refine_scs({'s': [20]})
320 | # self.check_refine_scs({'s': [10]})
321 | self.check_refine_scs({'q': [50]})
322 | self.check_refine_scs({'l': 10})
323 | # self.check_refine_scs({'l': 50})
324 | self.check_refine_scs({'l': 20, 'q': [10, 20]})
325 | # self.check_refine_scs({'l': 1000, 'q': [100] * 10})
326 | # self.check_refine_scs({'f': 10, 'l': 20, 'q': [10], 's': [10]})
327 | self.check_refine_scs({'f': 10, 'l': 20, 'q': [10, 20], 's': [5, 10]})
328 |
329 | def test_ecos(self):
330 | self.check_refine_ecos({'l': 20, 'q': [10] * 5})
331 | # self.check_refine_ecos({'l': 50, 'q': [10] * 10})
332 | self.check_refine_ecos({'q': [50]})
333 | self.check_refine_ecos({'l': 10})
334 | # self.check_refine_ecos({'l': 50})
335 | # self.check_refine_ecos({'l': 1000})
336 | self.check_refine_ecos({'l': 20, 'q': [10, 20, 40]})
337 | # self.check_refine_ecos({'l': 20, 'q': [10, 20, 40, 60]})
338 |
339 |
340 | class SparseLinalgTest(unittest.TestCase):
341 |
342 | def test_CSC(self):
343 |
344 | m, n = 40, 30
345 | A_csc = sp.random(m, n, density=.2, format='csc')
346 |
347 | b = np.random.randn(n)
348 | self.assertTrue(np.allclose(csc_matvec(A_csc.shape[0],
349 | A_csc.indptr,
350 | A_csc.indices,
351 | A_csc.data, b),
352 | A_csc @ b))
353 |
354 | def test_CSR(self):
355 |
356 | m, n = 40, 30
357 | A_csc = sp.random(m, n, density=.2, format='csr')
358 |
359 | b = np.random.randn(n)
360 | self.assertTrue(np.allclose(csr_matvec(A_csc.indptr,
361 | A_csc.indices,
362 | A_csc.data, b),
363 | A_csc @ b))
364 |
365 |
366 | class CVXPYTest(unittest.TestCase):
367 |
368 | def test_CVXPY(self):
369 |
370 | try:
371 | import cvxpy as cvx
372 | except ImportError:
373 | return
374 |
375 | m, n = 40, 30
376 | A = np.random.randn(m, n)
377 | b = np.random.randn(m)
378 | x = cvx.Variable(n)
379 |
380 | p = cvx.Problem(cvx.Minimize(cvx.sum_squares(x)),
381 | [cvx.log_sum_exp(x) <= 10, A @ x <= b])
382 |
383 | cvxpy_solve(p, presolve=True, iters=10, scs_opts={'eps': 1E-10})
384 |
385 | self.assertTrue(np.alltrue(A @ x.value - b <= 1E-8))
386 |
--------------------------------------------------------------------------------