├── .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 | --------------------------------------------------------------------------------