├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── bench └── bench_sparse_qr_extra.cpp ├── examples └── ellipse_fitting.cpp ├── imgs ├── benchmark_graph.png ├── benchmark_table.png ├── optim_conv.m4v ├── sparsity_patterns.png └── sparsity_patterns_2.png ├── src └── QRKit │ ├── BandedBlockedSparseQR.h │ ├── BlockAngularSparseQR.h │ ├── BlockDiagonalSparseQR.h │ ├── BlockMatrix1x2.h │ ├── BlockYTY.h │ ├── BlockedThinDenseQR.h │ ├── BlockedThinQRBase.h │ ├── BlockedThinSparseQR.h │ ├── QRKit │ ├── SparseBlockCOO.h │ ├── SparseBlockDiagonal.h │ ├── SparseBlockYTY.h │ ├── SparseQROrdering.h │ └── SparseQRUtils.h ├── test ├── CMakeLists.txt ├── test-qrkit.cpp ├── test-utils.cpp └── test.h └── user.cmake.in /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | build_linux/ 3 | user.cmake 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0.0) 2 | project(QRKit VERSION 0.1.0) 3 | 4 | include(user.cmake) # Create this from user.cmake.in 5 | find_package (Eigen3 3.3 REQUIRED NO_MODULE) 6 | 7 | #set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c++17 -O2 -fpermissive") 8 | #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -O2 -fpermissive") 9 | 10 | include_directories("src") 11 | 12 | add_subdirectory(test) 13 | # add_subdirectory(src) 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Jan Svoboda, Tom Cashman, Andrew Fitzgibbon, Sergio Garrido Jurado 2 | 3 | Mozilla Public License Version 2.0 4 | ================================== 5 | 6 | 1. Definitions 7 | -------------- 8 | 9 | 1.1. "Contributor" 10 | means each individual or legal entity that creates, contributes to 11 | the creation of, or owns Covered Software. 12 | 13 | 1.2. "Contributor Version" 14 | means the combination of the Contributions of others (if any) used 15 | by a Contributor and that particular Contributor's Contribution. 16 | 17 | 1.3. "Contribution" 18 | means Covered Software of a particular Contributor. 19 | 20 | 1.4. "Covered Software" 21 | means Source Code Form to which the initial Contributor has attached 22 | the notice in Exhibit A, the Executable Form of such Source Code 23 | Form, and Modifications of such Source Code Form, in each case 24 | including portions thereof. 25 | 26 | 1.5. "Incompatible With Secondary Licenses" 27 | means 28 | 29 | (a) that the initial Contributor has attached the notice described 30 | in Exhibit B to the Covered Software; or 31 | 32 | (b) that the Covered Software was made available under the terms of 33 | version 1.1 or earlier of the License, but not also under the 34 | terms of a Secondary License. 35 | 36 | 1.6. "Executable Form" 37 | means any form of the work other than Source Code Form. 38 | 39 | 1.7. "Larger Work" 40 | means a work that combines Covered Software with other material, in 41 | a separate file or files, that is not Covered Software. 42 | 43 | 1.8. "License" 44 | means this document. 45 | 46 | 1.9. "Licensable" 47 | means having the right to grant, to the maximum extent possible, 48 | whether at the time of the initial grant or subsequently, any and 49 | all of the rights conveyed by this License. 50 | 51 | 1.10. "Modifications" 52 | means any of the following: 53 | 54 | (a) any file in Source Code Form that results from an addition to, 55 | deletion from, or modification of the contents of Covered 56 | Software; or 57 | 58 | (b) any new file in Source Code Form that contains any Covered 59 | Software. 60 | 61 | 1.11. "Patent Claims" of a Contributor 62 | means any patent claim(s), including without limitation, method, 63 | process, and apparatus claims, in any patent Licensable by such 64 | Contributor that would be infringed, but for the grant of the 65 | License, by the making, using, selling, offering for sale, having 66 | made, import, or transfer of either its Contributions or its 67 | Contributor Version. 68 | 69 | 1.12. "Secondary License" 70 | means either the GNU General Public License, Version 2.0, the GNU 71 | Lesser General Public License, Version 2.1, the GNU Affero General 72 | Public License, Version 3.0, or any later versions of those 73 | licenses. 74 | 75 | 1.13. "Source Code Form" 76 | means the form of the work preferred for making modifications. 77 | 78 | 1.14. "You" (or "Your") 79 | means an individual or a legal entity exercising rights under this 80 | License. For legal entities, "You" includes any entity that 81 | controls, is controlled by, or is under common control with You. For 82 | purposes of this definition, "control" means (a) the power, direct 83 | or indirect, to cause the direction or management of such entity, 84 | whether by contract or otherwise, or (b) ownership of more than 85 | fifty percent (50%) of the outstanding shares or beneficial 86 | ownership of such entity. 87 | 88 | 2. License Grants and Conditions 89 | -------------------------------- 90 | 91 | 2.1. Grants 92 | 93 | Each Contributor hereby grants You a world-wide, royalty-free, 94 | non-exclusive license: 95 | 96 | (a) under intellectual property rights (other than patent or trademark) 97 | Licensable by such Contributor to use, reproduce, make available, 98 | modify, display, perform, distribute, and otherwise exploit its 99 | Contributions, either on an unmodified basis, with Modifications, or 100 | as part of a Larger Work; and 101 | 102 | (b) under Patent Claims of such Contributor to make, use, sell, offer 103 | for sale, have made, import, and otherwise transfer either its 104 | Contributions or its Contributor Version. 105 | 106 | 2.2. Effective Date 107 | 108 | The licenses granted in Section 2.1 with respect to any Contribution 109 | become effective for each Contribution on the date the Contributor first 110 | distributes such Contribution. 111 | 112 | 2.3. Limitations on Grant Scope 113 | 114 | The licenses granted in this Section 2 are the only rights granted under 115 | this License. No additional rights or licenses will be implied from the 116 | distribution or licensing of Covered Software under this License. 117 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 118 | Contributor: 119 | 120 | (a) for any code that a Contributor has removed from Covered Software; 121 | or 122 | 123 | (b) for infringements caused by: (i) Your and any other third party's 124 | modifications of Covered Software, or (ii) the combination of its 125 | Contributions with other software (except as part of its Contributor 126 | Version); or 127 | 128 | (c) under Patent Claims infringed by Covered Software in the absence of 129 | its Contributions. 130 | 131 | This License does not grant any rights in the trademarks, service marks, 132 | or logos of any Contributor (except as may be necessary to comply with 133 | the notice requirements in Section 3.4). 134 | 135 | 2.4. Subsequent Licenses 136 | 137 | No Contributor makes additional grants as a result of Your choice to 138 | distribute the Covered Software under a subsequent version of this 139 | License (see Section 10.2) or under the terms of a Secondary License (if 140 | permitted under the terms of Section 3.3). 141 | 142 | 2.5. Representation 143 | 144 | Each Contributor represents that the Contributor believes its 145 | Contributions are its original creation(s) or it has sufficient rights 146 | to grant the rights to its Contributions conveyed by this License. 147 | 148 | 2.6. Fair Use 149 | 150 | This License is not intended to limit any rights You have under 151 | applicable copyright doctrines of fair use, fair dealing, or other 152 | equivalents. 153 | 154 | 2.7. Conditions 155 | 156 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 157 | in Section 2.1. 158 | 159 | 3. Responsibilities 160 | ------------------- 161 | 162 | 3.1. Distribution of Source Form 163 | 164 | All distribution of Covered Software in Source Code Form, including any 165 | Modifications that You create or to which You contribute, must be under 166 | the terms of this License. You must inform recipients that the Source 167 | Code Form of the Covered Software is governed by the terms of this 168 | License, and how they can obtain a copy of this License. You may not 169 | attempt to alter or restrict the recipients' rights in the Source Code 170 | Form. 171 | 172 | 3.2. Distribution of Executable Form 173 | 174 | If You distribute Covered Software in Executable Form then: 175 | 176 | (a) such Covered Software must also be made available in Source Code 177 | Form, as described in Section 3.1, and You must inform recipients of 178 | the Executable Form how they can obtain a copy of such Source Code 179 | Form by reasonable means in a timely manner, at a charge no more 180 | than the cost of distribution to the recipient; and 181 | 182 | (b) You may distribute such Executable Form under the terms of this 183 | License, or sublicense it under different terms, provided that the 184 | license for the Executable Form does not attempt to limit or alter 185 | the recipients' rights in the Source Code Form under this License. 186 | 187 | 3.3. Distribution of a Larger Work 188 | 189 | You may create and distribute a Larger Work under terms of Your choice, 190 | provided that You also comply with the requirements of this License for 191 | the Covered Software. If the Larger Work is a combination of Covered 192 | Software with a work governed by one or more Secondary Licenses, and the 193 | Covered Software is not Incompatible With Secondary Licenses, this 194 | License permits You to additionally distribute such Covered Software 195 | under the terms of such Secondary License(s), so that the recipient of 196 | the Larger Work may, at their option, further distribute the Covered 197 | Software under the terms of either this License or such Secondary 198 | License(s). 199 | 200 | 3.4. Notices 201 | 202 | You may not remove or alter the substance of any license notices 203 | (including copyright notices, patent notices, disclaimers of warranty, 204 | or limitations of liability) contained within the Source Code Form of 205 | the Covered Software, except that You may alter any license notices to 206 | the extent required to remedy known factual inaccuracies. 207 | 208 | 3.5. Application of Additional Terms 209 | 210 | You may choose to offer, and to charge a fee for, warranty, support, 211 | indemnity or liability obligations to one or more recipients of Covered 212 | Software. However, You may do so only on Your own behalf, and not on 213 | behalf of any Contributor. You must make it absolutely clear that any 214 | such warranty, support, indemnity, or liability obligation is offered by 215 | You alone, and You hereby agree to indemnify every Contributor for any 216 | liability incurred by such Contributor as a result of warranty, support, 217 | indemnity or liability terms You offer. You may include additional 218 | disclaimers of warranty and limitations of liability specific to any 219 | jurisdiction. 220 | 221 | 4. Inability to Comply Due to Statute or Regulation 222 | --------------------------------------------------- 223 | 224 | If it is impossible for You to comply with any of the terms of this 225 | License with respect to some or all of the Covered Software due to 226 | statute, judicial order, or regulation then You must: (a) comply with 227 | the terms of this License to the maximum extent possible; and (b) 228 | describe the limitations and the code they affect. Such description must 229 | be placed in a text file included with all distributions of the Covered 230 | Software under this License. Except to the extent prohibited by statute 231 | or regulation, such description must be sufficiently detailed for a 232 | recipient of ordinary skill to be able to understand it. 233 | 234 | 5. Termination 235 | -------------- 236 | 237 | 5.1. The rights granted under this License will terminate automatically 238 | if You fail to comply with any of its terms. However, if You become 239 | compliant, then the rights granted under this License from a particular 240 | Contributor are reinstated (a) provisionally, unless and until such 241 | Contributor explicitly and finally terminates Your grants, and (b) on an 242 | ongoing basis, if such Contributor fails to notify You of the 243 | non-compliance by some reasonable means prior to 60 days after You have 244 | come back into compliance. Moreover, Your grants from a particular 245 | Contributor are reinstated on an ongoing basis if such Contributor 246 | notifies You of the non-compliance by some reasonable means, this is the 247 | first time You have received notice of non-compliance with this License 248 | from such Contributor, and You become compliant prior to 30 days after 249 | Your receipt of the notice. 250 | 251 | 5.2. If You initiate litigation against any entity by asserting a patent 252 | infringement claim (excluding declaratory judgment actions, 253 | counter-claims, and cross-claims) alleging that a Contributor Version 254 | directly or indirectly infringes any patent, then the rights granted to 255 | You by any and all Contributors for the Covered Software under Section 256 | 2.1 of this License shall terminate. 257 | 258 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 259 | end user license agreements (excluding distributors and resellers) which 260 | have been validly granted by You or Your distributors under this License 261 | prior to termination shall survive termination. 262 | 263 | ************************************************************************ 264 | * * 265 | * 6. Disclaimer of Warranty * 266 | * ------------------------- * 267 | * * 268 | * Covered Software is provided under this License on an "as is" * 269 | * basis, without warranty of any kind, either expressed, implied, or * 270 | * statutory, including, without limitation, warranties that the * 271 | * Covered Software is free of defects, merchantable, fit for a * 272 | * particular purpose or non-infringing. The entire risk as to the * 273 | * quality and performance of the Covered Software is with You. * 274 | * Should any Covered Software prove defective in any respect, You * 275 | * (not any Contributor) assume the cost of any necessary servicing, * 276 | * repair, or correction. This disclaimer of warranty constitutes an * 277 | * essential part of this License. No use of any Covered Software is * 278 | * authorized under this License except under this disclaimer. * 279 | * * 280 | ************************************************************************ 281 | 282 | ************************************************************************ 283 | * * 284 | * 7. Limitation of Liability * 285 | * -------------------------- * 286 | * * 287 | * Under no circumstances and under no legal theory, whether tort * 288 | * (including negligence), contract, or otherwise, shall any * 289 | * Contributor, or anyone who distributes Covered Software as * 290 | * permitted above, be liable to You for any direct, indirect, * 291 | * special, incidental, or consequential damages of any character * 292 | * including, without limitation, damages for lost profits, loss of * 293 | * goodwill, work stoppage, computer failure or malfunction, or any * 294 | * and all other commercial damages or losses, even if such party * 295 | * shall have been informed of the possibility of such damages. This * 296 | * limitation of liability shall not apply to liability for death or * 297 | * personal injury resulting from such party's negligence to the * 298 | * extent applicable law prohibits such limitation. Some * 299 | * jurisdictions do not allow the exclusion or limitation of * 300 | * incidental or consequential damages, so this exclusion and * 301 | * limitation may not apply to You. * 302 | * * 303 | ************************************************************************ 304 | 305 | 8. Litigation 306 | ------------- 307 | 308 | Any litigation relating to this License may be brought only in the 309 | courts of a jurisdiction where the defendant maintains its principal 310 | place of business and such litigation shall be governed by laws of that 311 | jurisdiction, without reference to its conflict-of-law provisions. 312 | Nothing in this Section shall prevent a party's ability to bring 313 | cross-claims or counter-claims. 314 | 315 | 9. Miscellaneous 316 | ---------------- 317 | 318 | This License represents the complete agreement concerning the subject 319 | matter hereof. If any provision of this License is held to be 320 | unenforceable, such provision shall be reformed only to the extent 321 | necessary to make it enforceable. Any law or regulation which provides 322 | that the language of a contract shall be construed against the drafter 323 | shall not be used to construe this License against a Contributor. 324 | 325 | 10. Versions of the License 326 | --------------------------- 327 | 328 | 10.1. New Versions 329 | 330 | Mozilla Foundation is the license steward. Except as provided in Section 331 | 10.3, no one other than the license steward has the right to modify or 332 | publish new versions of this License. Each version will be given a 333 | distinguishing version number. 334 | 335 | 10.2. Effect of New Versions 336 | 337 | You may distribute the Covered Software under the terms of the version 338 | of the License under which You originally received the Covered Software, 339 | or under the terms of any subsequent version published by the license 340 | steward. 341 | 342 | 10.3. Modified Versions 343 | 344 | If you create software not governed by this License, and you want to 345 | create a new license for such software, you may create and use a 346 | modified version of this License if you rename the license and remove 347 | any references to the name of the license steward (except to note that 348 | such modified license differs from this License). 349 | 350 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 351 | Licenses 352 | 353 | If You choose to distribute Source Code Form that is Incompatible With 354 | Secondary Licenses under the terms of this version of the License, the 355 | notice described in Exhibit B of this License must be attached. 356 | 357 | Exhibit A - Source Code Form License Notice 358 | ------------------------------------------- 359 | 360 | This Source Code Form is subject to the terms of the Mozilla Public 361 | License, v. 2.0. If a copy of the MPL was not distributed with this 362 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 363 | 364 | If it is not possible or desirable to put the notice in a particular 365 | file, then You may include the notice in a location (such as a LICENSE 366 | file in a relevant directory) where a recipient would be likely to look 367 | for such a notice. 368 | 369 | You may add additional accurate notices of copyright ownership. 370 | 371 | Exhibit B - "Incompatible With Secondary Licenses" Notice 372 | --------------------------------------------------------- 373 | 374 | This Source Code Form is "Incompatible With Secondary Licenses", as 375 | defined by the Mozilla Public License, v. 2.0. 376 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QRKit 2 | 3 | This repository contains implementation of our WACV 2018 work [QRkit: Sparse, Composable QR Decompositions for Efficient and Stable Solutions to Problems in Computer Vision 4 | ](https://arxiv.org/abs/1802.03773). 5 | 6 | QRKit is an extension of Eigen C++ library that allows for efficient QR decomposition of sparse matrices with common sparsity patterns. 7 | 8 | ## Building 9 | ```sh 10 | $ cp user.cmake.in user.cmake 11 | $ vi user.cmake # Edit user.cmake to point to your Eigen distro 12 | $ mkdir build 13 | $ cd build 14 | $ cmake .. 15 | $ cmake --build . 16 | ``` 17 | 18 | ## Common sparsity patterns 19 | The common sparsity patterns QRKit can bring significant speed-up for are shown below. 20 |

21 | 22 | 23 |

24 | 25 | ## Benchmark 26 | A simple benchmark showing speed-up potential of QRKit for matrix with a block-diagonal sparsity pattern. 27 |

28 | 29 | 30 |

31 | 32 | ## Reference 33 | If you make any use of our code or data, please cite the following: 34 | ``` 35 | @conference{svoboda2018qrkit, 36 | title={QRkit: Sparse, Composable QR Decompositions for Efficient and Stable Solutions to Problems in Computer Vision}, 37 | author={Svoboda, J. and Cashman, T. J. and Fitzgibbon, A. W.}, 38 | booktitle={2018 IEEE Winter Conference on Applications of Computer Vision (WACV)}, 39 | year={2018}, 40 | } 41 | ``` 42 | 43 | ## Acknowledgments 44 | For any reuse and or redistribution of the code in this repository please follow the [license agreement](./LICENSE) attached to this repository. 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /bench/bench_sparse_qr_extra.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of Eigen, a lightweight C++ template library 2 | // for linear algebra. 3 | // 4 | // Copyright (C) 2017 Jan Svoboda 5 | // Copyright (C) 2016 Andrew Fitzgibbon 6 | // Copyright (C) 2016 Sergio Garrido Jurado 7 | // 8 | // This Source Code Form is subject to the terms of the Mozilla 9 | // Public License v. 2.0. If a copy of the MPL was not distributed 10 | // with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 11 | 12 | 13 | // import basic and product tests for deprectaed DynamicSparseMatrix 14 | #define EIGEN_NO_DEPRECATED_WARNING 15 | 16 | //#define WITH_SPQR 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "eigen/test/main.h" 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #ifdef WITH_SPQR 32 | #include 33 | #endif 34 | 35 | #include 36 | #include 37 | 38 | 39 | using namespace Eigen; 40 | using namespace QRKit; 41 | 42 | #ifdef WITH_SPQR 43 | typedef SuiteSparse_long IndexType; 44 | #else 45 | typedef int IndexType; 46 | #endif 47 | 48 | typedef SparseMatrix JacobianType; 49 | typedef Matrix MatrixType; 50 | 51 | template 52 | struct EllipseFitting : SparseFunctor<_Scalar, IndexType> 53 | { 54 | // Class data: 2xN matrix with each column a 2D point 55 | Matrix2Xd ellipsePoints; 56 | 57 | // Number of parameters in the model, to which will be added 58 | // one latent variable per point. 59 | static const int nParamsModel = 5; 60 | 61 | // Constructor initializes points, and tells the base class how many parameters there are in total 62 | EllipseFitting(const Matrix2Xd& points) : 63 | SparseFunctor<_Scalar, IndexType>(nParamsModel + points.cols(), points.cols() * 2), 64 | ellipsePoints(points) 65 | { 66 | } 67 | 68 | // Functor functions 69 | int operator()(const InputType& uv, ValueType& fvec) const { 70 | // Ellipse parameters are the last 5 entries 71 | auto params = uv.tail(nParamsModel); 72 | double a = params[0]; 73 | double b = params[1]; 74 | double x0 = params[2]; 75 | double y0 = params[3]; 76 | double r = params[4]; 77 | 78 | // Correspondences (t values) are the first N 79 | for (int i = 0; i < ellipsePoints.cols(); i++) { 80 | double t = uv(i); 81 | double x = a*cos(t)*cos(r) - b*sin(t)*sin(r) + x0; 82 | double y = a*cos(t)*sin(r) + b*sin(t)*cos(r) + y0; 83 | fvec(2 * i + 0) = ellipsePoints(0, i) - x; 84 | fvec(2 * i + 1) = ellipsePoints(1, i) - y; 85 | } 86 | 87 | return 0; 88 | } 89 | 90 | // Functor jacobian 91 | int df(const InputType& uv, JacobianType& fjac) { 92 | // X_i - (a*cos(t_i) + x0) 93 | // Y_i - (b*sin(t_i) + y0) 94 | int npoints = ellipsePoints.cols(); 95 | auto params = uv.tail(nParamsModel); 96 | double a = params[0]; 97 | double b = params[1]; 98 | double r = params[4]; 99 | 100 | TripletArray triplets(npoints * 2 * 5); // npoints * rows_per_point * nonzeros_per_row 101 | for (int i = 0; i < npoints; i++) { 102 | double t = uv(i); 103 | triplets.add(2 * i, i, +a*cos(r)*sin(t) + b*sin(r)*cos(t)); 104 | triplets.add(2 * i, npoints + 0, -cos(t)*cos(r)); 105 | triplets.add(2 * i, npoints + 1, +sin(t)*sin(r)); 106 | triplets.add(2 * i, npoints + 2, -1); 107 | triplets.add(2 * i, npoints + 4, +a*cos(t)*sin(r) + b*sin(t)*cos(r)); 108 | 109 | triplets.add(2 * i + 1, i, +a*sin(r)*sin(t) - b*cos(r)*cos(t)); 110 | triplets.add(2 * i + 1, npoints + 0, -cos(t)*sin(r)); 111 | triplets.add(2 * i + 1, npoints + 1, -sin(t)*cos(r)); 112 | triplets.add(2 * i + 1, npoints + 3, -1); 113 | triplets.add(2 * i + 1, npoints + 4, -a*cos(t)*cos(r) + b*sin(t)*sin(r)); 114 | } 115 | 116 | fjac.setFromTriplets(triplets.begin(), triplets.end()); 117 | return 0; 118 | } 119 | }; 120 | 121 | template 122 | struct SparseQR_EllipseFitting : public EllipseFitting<_Scalar> { 123 | // For generic Jacobian, one might use this Dense QR solver. 124 | typedef SparseQR > GeneralQRSolver; 125 | typedef GeneralQRSolver QRSolver; 126 | 127 | SparseQR_EllipseFitting(const Matrix2Xd& points) : 128 | EllipseFitting<_Scalar>(points) { 129 | } 130 | 131 | void initQRSolver(GeneralQRSolver &qr) { 132 | // set left block size 133 | } 134 | }; 135 | 136 | #ifdef WITH_SPQR 137 | template 138 | struct SPQR_EllipseFitting : public EllipseFitting<_Scalar> { 139 | typedef SPQR SPQRSolver; 140 | typedef SPQRSolver QRSolver; 141 | 142 | SPQR_EllipseFitting(const Matrix2Xd& points) : 143 | EllipseFitting<_Scalar>(points) { 144 | } 145 | 146 | // And tell the algorithm how to set the QR parameters. 147 | void initQRSolver(SPQRSolver &qr) { 148 | 149 | } 150 | }; 151 | #endif 152 | 153 | template 154 | struct SparseBlockDiagonalQR_EllipseFitting : public EllipseFitting<_Scalar> { 155 | // QR for J1 subblocks is 2x1 156 | typedef ColPivHouseholderQR > DenseQRSolver2x1; 157 | // QR for J1 is block diagonal 158 | typedef BlockDiagonalSparseQR LeftSuperBlockSolver; 159 | // QR for J1'J2 is general dense (faster than general sparse by about 1.5x for n=500K) 160 | typedef ColPivHouseholderQR > RightSuperBlockSolver; 161 | // QR for J is concatenation of the above. 162 | typedef BlockAngularSparseQR SchurlikeQRSolver; 163 | 164 | typedef SchurlikeQRSolver QRSolver; 165 | 166 | 167 | SparseBlockDiagonalQR_EllipseFitting(const Matrix2Xd& points) : 168 | EllipseFitting<_Scalar>(points) { 169 | } 170 | 171 | // And tell the algorithm how to set the QR parameters. 172 | void initQRSolver(SchurlikeQRSolver &qr) { 173 | } 174 | }; 175 | 176 | template 177 | struct SparseBlockBandedQR_EllipseFitting : public EllipseFitting<_Scalar> { 178 | typedef HouseholderQR> BandBlockQRSolver; 179 | typedef BandedBlockedSparseQR BandedBlockedQRSolver; 180 | // QR for J1 is banded blocked QR 181 | typedef BandedBlockedQRSolver LeftSuperBlockSolver; 182 | // QR for J1'J2 is general dense (faster than general sparse by about 1.5x for n=500K) 183 | typedef ColPivHouseholderQR> RightSuperBlockSolver; 184 | // QR solver for sparse block angular matrix 185 | typedef BlockAngularSparseQR BlockAngularQRSolver; 186 | 187 | typedef BlockAngularQRSolver QRSolver; 188 | 189 | SparseBlockBandedQR_EllipseFitting(const Matrix2Xd& points) : 190 | EllipseFitting<_Scalar>(points) { 191 | } 192 | 193 | void initQRSolver(BlockAngularQRSolver &qr) { 194 | } 195 | }; 196 | 197 | typedef EllipseFitting::InputType ParamsType; 198 | 199 | void printParamsHeader() { 200 | std::cout << "a \t"; 201 | std::cout << "b \t"; 202 | std::cout << "x0\t"; 203 | std::cout << "y0\t"; 204 | std::cout << "r \t"; 205 | std::cout << "Duration"; 206 | std::cout << std::endl; 207 | } 208 | 209 | void printParams(ParamsType ¶ms, int nDataPoints, double duration = -1.) { 210 | std::cout << params(nDataPoints) << "\t"; 211 | std::cout << params(nDataPoints + 1) << "\t"; 212 | std::cout << params(nDataPoints + 2) << "\t"; 213 | std::cout << params(nDataPoints + 3) << "\t"; 214 | std::cout << params(nDataPoints + 4)*180. / EIGEN_PI << "\t"; 215 | if (duration >= 0) { 216 | std::cout << duration << "s"; 217 | } 218 | std::cout << std::endl; 219 | } 220 | 221 | void initializeParams(int nDataPoints, const Matrix2Xd &ellipsePoints, double incr, ParamsType ¶ms) { 222 | params.resize(EllipseFitting::nParamsModel + nDataPoints); 223 | double minX, minY, maxX, maxY; 224 | minX = maxX = ellipsePoints(0, 0); 225 | minY = maxY = ellipsePoints(1, 0); 226 | for (int i = 0; i ruled out for now... 247 | int NumSamplePoints[NumTests] = { 500, 1000, 2000, 5000, 10000, 50000, 100000, 500000 }; 248 | 249 | int main() { 250 | /***************************************************************************/ 251 | std::cout << "################### Ellipse fitting benchmark #######################" << std::endl; 252 | std::cout << "#####################################################################" << std::endl; 253 | std::cout << "N - Number of data points" << std::endl; 254 | std::cout << "Sparse QR - Eigen's Sparse QR" << std::endl; 255 | std::cout << "SuiteSparse QR - SuiteSparse QR" << std::endl; 256 | std::cout << "Bl Diag Sp QR - Block Diagonal Sparse QR" << std::endl; 257 | std::cout << "Sp Band Bl QR - Sparse Banded Blocked QR" << std::endl; 258 | std::cout << "#####################################################################" << std::endl; 259 | for (int i = 0; i < NumTests; i++) { 260 | // Create the ellipse paramteers and data points 261 | // ELLIPSE PARAMETERS 262 | double a, b, x0, y0, r; 263 | a = 7.5; 264 | b = 2; 265 | x0 = 17.; 266 | y0 = 23.; 267 | r = 0.23; 268 | 269 | std::cout << "N = " << NumSamplePoints[i] << " \t"; 270 | printParamsHeader(); 271 | std::cout << "=====================================================================" << std::endl; 272 | 273 | // CREATE DATA SAMPLES 274 | int nDataPoints = NumSamplePoints[i]; 275 | Matrix2Xd ellipsePoints; 276 | ellipsePoints.resize(2, nDataPoints); 277 | Scalar incr = 1.3*EIGEN_PI / Scalar(nDataPoints); 278 | for (int i = 0; i SparseQRFunctor; 307 | SparseQRFunctor functor1(ellipsePoints); 308 | Eigen::LevenbergMarquardt< SparseQRFunctor > lm1(functor1); 309 | lm1.setVerbose(LM_VERBOSE); 310 | begin = clock(); 311 | info = lm1.minimize(params); 312 | duration = double(clock() - begin) / CLOCKS_PER_SEC; 313 | std::cout << "Sparse QR:\t"; 314 | printParams(params, nDataPoints, duration); 315 | } 316 | 317 | #ifdef WITH_SPQR 318 | if (NumSamplePoints[i] <= SPQR_LimitN) { 319 | initializeParams(nDataPoints, ellipsePoints, incr, params); 320 | typedef SPQR_EllipseFitting SPQRFunctor; 321 | SPQRFunctor functor2(ellipsePoints); 322 | Eigen::LevenbergMarquardt< SPQRFunctor > lm2(functor2); 323 | lm2.setVerbose(LM_VERBOSE); 324 | begin = clock(); 325 | info = lm2.minimize(params); 326 | duration = double(clock() - begin) / CLOCKS_PER_SEC; 327 | std::cout << "SuiteSparse QR:\t"; 328 | printParams(params, nDataPoints, duration); 329 | } 330 | #endif 331 | 332 | initializeParams(nDataPoints, ellipsePoints, incr, params); 333 | typedef SparseBlockDiagonalQR_EllipseFitting SparseBlockDiagonalQRFunctor; 334 | SparseBlockDiagonalQRFunctor functor3(ellipsePoints); 335 | Eigen::LevenbergMarquardt< SparseBlockDiagonalQRFunctor > lm3(functor3); 336 | lm3.setVerbose(LM_VERBOSE); 337 | begin = clock(); 338 | info = lm3.minimize(params); 339 | duration = double(clock() - begin) / CLOCKS_PER_SEC; 340 | std::cout << "Bl Diag Sp QR:\t"; 341 | printParams(params, nDataPoints, duration); 342 | 343 | initializeParams(nDataPoints, ellipsePoints, incr, params); 344 | typedef SparseBlockBandedQR_EllipseFitting SparseBlockBandedQRFunctor; 345 | SparseBlockBandedQRFunctor functor4(ellipsePoints); 346 | Eigen::LevenbergMarquardt< SparseBlockBandedQRFunctor > lm4(functor4); 347 | lm4.setVerbose(LM_VERBOSE); 348 | begin = clock(); 349 | info = lm4.minimize(params); 350 | duration = double(clock() - begin) / CLOCKS_PER_SEC; 351 | std::cout << "Sp Band Bl QR:\t"; 352 | printParams(params, nDataPoints, duration); 353 | std::cout << "#####################################################################" << std::endl; 354 | } 355 | 356 | return 0; 357 | } 358 | 359 | -------------------------------------------------------------------------------- /examples/ellipse_fitting.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of Eigen, a lightweight C++ template library 2 | // for linear algebra. 3 | // 4 | // Copyright (C) 2017 Jan Svoboda 5 | // Copyright (C) 2016 Andrew Fitzgibbon 6 | // Copyright (C) 2016 Sergio Garrido Jurado 7 | // 8 | // This Source Code Form is subject to the terms of the Mozilla 9 | // Public License v. 2.0. If a copy of the MPL was not distributed 10 | // with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 11 | 12 | 13 | // import basic and product tests for deprecated DynamicSparseMatrix 14 | #define EIGEN_NO_DEPRECATED_WARNING 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include "main.h" 23 | //#include "sparse_basic.cpp" 24 | //#include "sparse_product.cpp" 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | using namespace Eigen; 32 | using namespace QRKit; 33 | 34 | // Eigen's better DenseQR: 35 | typedef ColPivHouseholderQR > DenseQR; 36 | 37 | typedef int IndexType; 38 | 39 | typedef double Scalar; 40 | 41 | typedef SparseMatrix JacobianType; 42 | typedef Matrix DenseMatrixType; 43 | 44 | template 45 | struct EllipseFitting : SparseFunctor<_Scalar, IndexType> 46 | { 47 | // Class data: 2xN matrix with each column a 2D point 48 | Matrix2Xd ellipsePoints; 49 | 50 | // Number of parameters in the model, to which will be added 51 | // one latent variable per point. 52 | static const int nParamsModel = 5; 53 | 54 | // Constructor initializes points, and tells the base class how many parameters there are in total 55 | EllipseFitting(const Matrix2Xd& points) : 56 | SparseFunctor<_Scalar, IndexType>(nParamsModel + points.cols(), points.cols() * 2), 57 | ellipsePoints(points) 58 | { 59 | } 60 | 61 | // Functor functions 62 | int operator()(const InputType& uv, ValueType& fvec) const 63 | { 64 | // Ellipse parameters are the last 5 entries 65 | auto params = uv.tail(nParamsModel); 66 | double a = params[0]; 67 | double b = params[1]; 68 | double x0 = params[2]; 69 | double y0 = params[3]; 70 | double r = params[4]; 71 | 72 | // Correspondences (t values) are the first N 73 | for (int i = 0; i < ellipsePoints.cols(); i++) { 74 | double t = uv(i); 75 | double x = a*cos(t)*cos(r) - b*sin(t)*sin(r) + x0; 76 | double y = a*cos(t)*sin(r) + b*sin(t)*cos(r) + y0; 77 | fvec(2 * i + 0) = ellipsePoints(0, i) - x; 78 | fvec(2 * i + 1) = ellipsePoints(1, i) - y; 79 | } 80 | 81 | return 0; 82 | } 83 | 84 | // Functor jacobian 85 | int df(const InputType& uv, JacobianType& fjac) 86 | { 87 | // X_i - (a*cos(t_i) + x0) 88 | // Y_i - (b*sin(t_i) + y0) 89 | int npoints = ellipsePoints.cols(); 90 | auto params = uv.tail(nParamsModel); 91 | double a = params[0]; 92 | double b = params[1]; 93 | double r = params[4]; 94 | 95 | TripletArray triplets(npoints * 2 * 5); // npoints * rows_per_point * nonzeros_per_row 96 | for (int i = 0; i < npoints; i++) { 97 | double t = uv(i); 98 | triplets.add(2 * i, i, +a*cos(r)*sin(t) + b*sin(r)*cos(t)); 99 | triplets.add(2 * i, npoints + 0, -cos(t)*cos(r)); 100 | triplets.add(2 * i, npoints + 1, +sin(t)*sin(r)); 101 | triplets.add(2 * i, npoints + 2, -1); 102 | triplets.add(2 * i, npoints + 4, +a*cos(t)*sin(r) + b*sin(t)*cos(r)); 103 | 104 | triplets.add(2 * i + 1, i, +a*sin(r)*sin(t) - b*cos(r)*cos(t)); 105 | triplets.add(2 * i + 1, npoints + 0, -cos(t)*sin(r)); 106 | triplets.add(2 * i + 1, npoints + 1, -sin(t)*cos(r)); 107 | triplets.add(2 * i + 1, npoints + 3, -1); 108 | triplets.add(2 * i + 1, npoints + 4, -a*cos(t)*cos(r) + b*sin(t)*sin(r)); 109 | } 110 | 111 | fjac.setFromTriplets(triplets.begin(), triplets.end()); 112 | return 0; 113 | } 114 | }; 115 | 116 | template 117 | struct SparseBlockDiagonalQR_EllipseFitting : public EllipseFitting<_Scalar> { 118 | // QR for J1 subblocks is 2x1 119 | typedef Matrix DenseMatrix2x1; 120 | typedef ColPivHouseholderQR DenseQRSolver2x1; 121 | // QR for J1 is block diagonal 122 | typedef BlockDiagonalSparseQR LeftSuperBlockSolver; 123 | // QR for J1'J2 is general dense (faster than general sparse by about 1.5x for n=500K) 124 | typedef DenseQR RightSuperBlockSolver; 125 | 126 | class QRSolver : public BlockAngularSparseQR { 127 | const int RightBlockColumns = nParamsModel; 128 | public: 129 | // Solver has to know how to treat input general SparseMatrix 130 | QRSolver(const JacobianType& mat) 131 | { 132 | // Left block is sparse block diagonal matrix 133 | JacobianType leftBlock = mat.block(0, 0, mat.rows(), mat.cols() - RightBlockColumns); 134 | SparseBlockDiagonal leftBlockDiag; 135 | leftBlockDiag.fromBlockDiagonalPattern(leftBlock, 2, 1); 136 | // Right block is general DenseMatrix 137 | DenseMatrixType rightBlock = mat.block(0, mat.cols() - RightBlockColumns, mat.rows(), RightBlockColumns); 138 | // Input to the block angular solver is sparse block angular matrix 139 | BlockMatrix1x2, DenseMatrixType> blkAngular(leftBlockDiag, rightBlock); 140 | this->compute(blkAngular); 141 | } 142 | }; 143 | 144 | SparseBlockDiagonalQR_EllipseFitting(const Matrix2Xd& points) : 145 | EllipseFitting<_Scalar>(points) { 146 | } 147 | }; 148 | 149 | template 150 | struct SparseBlockBandedQR_EllipseFitting : public EllipseFitting<_Scalar> 151 | { 152 | typedef Matrix DenseMatrix2x1; 153 | typedef HouseholderQR BandBlockQRSolver; 154 | typedef BandedBlockedSparseQR BandedBlockedQRSolver; 155 | // QR for J1 is banded blocked QR 156 | typedef BandedBlockedQRSolver LeftSuperBlockSolver; 157 | // QR for J1'J2 is general dense (faster than general sparse by about 1.5x for n=500K) 158 | typedef ColPivHouseholderQR> RightSuperBlockSolver; 159 | 160 | // Define the full QR solver 161 | class QRSolver : public BlockAngularSparseQR { 162 | const int RightBlockColumns = 5; 163 | public: 164 | // Solver has to know how to treat input general SparseMatrix 165 | QRSolver(const JacobianType& mat) 166 | { 167 | // Left block is general SparseMatrix 168 | JacobianType leftBlock = mat.block(0, 0, mat.rows(), mat.cols() - RightBlockColumns); 169 | // Right block is general DenseMatrix 170 | DenseMatrixType rightBlock = mat.block(0, mat.cols() - RightBlockColumns, mat.rows(), RightBlockColumns); 171 | // Input to the block angular solver is sparse block angular matrix 172 | BlockMatrix1x2 blkAngular(leftBlock, rightBlock); 173 | this->compute(blkAngular); 174 | } 175 | }; 176 | 177 | SparseBlockBandedQR_EllipseFitting(const Matrix2Xd& points) : 178 | EllipseFitting<_Scalar>(points) { 179 | } 180 | }; 181 | 182 | typedef EllipseFitting::InputType ParamsType; 183 | 184 | void printParamsHeader() 185 | { 186 | std::cout << "a \t"; 187 | std::cout << "b \t"; 188 | std::cout << "x0\t"; 189 | std::cout << "y0\t"; 190 | std::cout << "r \t"; 191 | std::cout << "Duration"; 192 | std::cout << std::endl; 193 | } 194 | 195 | void printParams(ParamsType ¶ms, int npoints, double duration = -1.) 196 | { 197 | std::cout << params(npoints) << "\t"; 198 | std::cout << params(npoints + 1) << "\t"; 199 | std::cout << params(npoints + 2) << "\t"; 200 | std::cout << params(npoints + 3) << "\t"; 201 | std::cout << params(npoints + 4)*180. / EIGEN_PI << "\t"; 202 | if (duration >= 0) { 203 | std::cout << duration << "s"; 204 | } 205 | std::cout << std::endl; 206 | } 207 | 208 | void initializeParams(const Matrix2Xd &ellipsePoints, double incr, ParamsType ¶ms) 209 | { 210 | int npoints = ellipsePoints.cols(); 211 | 212 | params.resize(EllipseFitting::nParamsModel + npoints); 213 | 214 | // TODO: use Eigen's max/min over columns mat.colwise().maxCoeff() 215 | double minX, minY, maxX, maxY; 216 | minX = maxX = ellipsePoints(0, 0); 217 | minY = maxY = ellipsePoints(1, 0); 218 | for (int i = 0; i fabs(params(npoints))) { 241 | std::swap(params(npoints), params(npoints + 1)); 242 | params(npoints + 4) -= 0.5*EIGEN_PI; 243 | } 244 | // a and b should be positive 245 | if (params(npoints)<0) { 246 | params(npoints) *= -1.; 247 | params(npoints + 1) *= -1.; 248 | params(npoints + 4) += EIGEN_PI; 249 | } 250 | // fix rotation angle range 251 | while (params(npoints + 4) < 0) params(npoints + 4) += 2.*EIGEN_PI; 252 | while (params(npoints + 4) > EIGEN_PI) params(npoints + 4) -= EIGEN_PI; 253 | } 254 | 255 | void test_block_diagonal(const Ellipse &el, const Matrix2Xd &ellipsePoints, ParamsType ¶ms) 256 | { 257 | Eigen::LevenbergMarquardtSpace::Status info; 258 | typedef SparseBlockDiagonalQR_EllipseFitting SparseBlockDiagonalQRFunctor; 259 | SparseBlockDiagonalQRFunctor functor3(ellipsePoints); 260 | Eigen::LevenbergMarquardt< SparseBlockDiagonalQRFunctor > lm3(functor3); 261 | info = lm3.minimize(params); 262 | 263 | checkParamsAmbiguity(ellipsePoints, params); 264 | 265 | VERIFY_IS_APPROX(el.a, params(ellipsePoints.cols())); 266 | VERIFY_IS_APPROX(el.b, params(ellipsePoints.cols() + 1)); 267 | VERIFY_IS_APPROX(el.x0, params(ellipsePoints.cols() + 2)); 268 | VERIFY_IS_APPROX(el.y0, params(ellipsePoints.cols() + 3)); 269 | VERIFY_IS_APPROX(el.r, params(ellipsePoints.cols() + 4)); 270 | } 271 | 272 | void test_banded_blocked(const Ellipse &el, const Matrix2Xd &ellipsePoints, ParamsType ¶ms) 273 | { 274 | Eigen::LevenbergMarquardtSpace::Status info; 275 | typedef SparseBlockBandedQR_EllipseFitting SparseBlockBandedQRFunctor; 276 | SparseBlockBandedQRFunctor functor4(ellipsePoints); 277 | Eigen::LevenbergMarquardt lm4(functor4); 278 | info = lm4.minimize(params); 279 | 280 | checkParamsAmbiguity(ellipsePoints, params); 281 | 282 | VERIFY_IS_APPROX(el.a, params(ellipsePoints.cols())); 283 | VERIFY_IS_APPROX(el.b, params(ellipsePoints.cols() + 1)); 284 | VERIFY_IS_APPROX(el.x0, params(ellipsePoints.cols() + 2)); 285 | VERIFY_IS_APPROX(el.y0, params(ellipsePoints.cols() + 3)); 286 | VERIFY_IS_APPROX(el.r, params(ellipsePoints.cols() + 4)); 287 | } 288 | 289 | // TODO: this looks like it's not used below? 290 | struct Ellipse { 291 | Ellipse() 292 | : a(1), b(1), x0(0), y0(0), r(1) { 293 | } 294 | 295 | Ellipse(const double a, const double b, const double x0, const double y0, const double r) 296 | : a(a), b(b), x0(x0), y0(y0), r(r) { 297 | } 298 | 299 | double a; 300 | double b; 301 | double x0; 302 | double y0; 303 | double r; 304 | }; 305 | 306 | void test_sparse_qr_extra_lm_fitting() 307 | { 308 | const int NumSamplePoints = 50000; 309 | // Create the ellipse parameters and data points 310 | // ELLIPSE PARAMETERS 311 | Ellipse el(7.5, 2, 17, 23., 0.23); 312 | 313 | // CREATE DATA SAMPLES 314 | int npoints = NumSamplePoints; 315 | Matrix2Xd ellipsePoints; 316 | ellipsePoints.resize(2, npoints); 317 | Scalar incr = 1.3*EIGEN_PI / Scalar(npoints); 318 | for (int i = 0; i 5 | // Copyright (C) 2016 Andrew Fitzgibbon 6 | // Copyright (C) 2016 Sergio Garrido Jurado 7 | // Copyright (C) 2012-2013 Desire Nuentsa 8 | // Copyright (C) 2012-2014 Gael Guennebaud 9 | // 10 | // This Source Code Form is subject to the terms of the Mozilla 11 | // Public License v. 2.0. If a copy of the MPL was not distributed 12 | // with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 13 | 14 | #ifndef QRKIT_BLOCK_DIAGONAL_SPARSE_QR_H 15 | #define QRKIT_BLOCK_DIAGONAL_SPARSE_QR_H 16 | 17 | #include "SparseQRUtils.h" 18 | #include "SparseQROrdering.h" 19 | #include "SparseBlockDiagonal.h" 20 | 21 | namespace QRKit { 22 | 23 | /** 24 | * \ingroup SparseQR_Module 25 | * \class BlockDiagonalSparseQR 26 | * \brief QR factorization of block-diagonal matrix 27 | * 28 | * Performs QR factorization of a block-diagonal matrix and creates matrices Q and R accordingly. 29 | * Template parameter _QFormat specifies the ordering of Q and R: 30 | * MatrixQFormat::FullQ - orthogonal Q, corresponding R is upper triangular (default) 31 | * MatrixQFormat::BlockDiagonalQ - orthogonal block diagonal Q, corresponding R is not upper triangular 32 | * There exists a permutation that allows to convert between FulLQ and BlockDiagonalQ. 33 | * 34 | * \implsparsesolverconcept 35 | * 36 | */ 37 | template 38 | class BlockDiagonalSparseQR : public SparseSolverBase > 39 | { 40 | protected: 41 | typedef SparseSolverBase > Base; 42 | using Base::m_isInitialized; 43 | public: 44 | using Base::_solve_impl; 45 | typedef _BlockQRSolver BlockQRSolver; 46 | typedef typename BlockQRSolver::MatrixType BlockMatrixType; 47 | typedef SparseBlockDiagonal MatrixType; 48 | typedef typename MatrixType::Scalar Scalar; 49 | typedef typename MatrixType::RealScalar RealScalar; 50 | typedef typename MatrixType::StorageIndex StorageIndex; 51 | typedef typename MatrixType::Index Index; 52 | typedef Matrix IndexVector; 53 | typedef Matrix ScalarVector; 54 | 55 | typedef SparseMatrix MatrixQType; 56 | typedef SparseMatrix MatrixRType; 57 | typedef PermutationMatrix PermutationType; 58 | 59 | enum MatrixQFormat { 60 | FullQ = 0, 61 | BlockDiagonalQ = 1 62 | }; 63 | /* 64 | * Stores information about a dense block in a block sparse matrix. 65 | * Holds the position of the block (row index, column index) and its size (number of rows, number of columns). 66 | */ 67 | typedef typename SparseQRUtils::BlockBandedMatrixInfo BlockBandedMatrixInfo; 68 | 69 | enum { 70 | ColsAtCompileTime = Dynamic,//MatrixType::ColsAtCompileTime, 71 | MaxColsAtCompileTime = Dynamic//MatrixType::MaxColsAtCompileTime 72 | }; 73 | 74 | public: 75 | BlockDiagonalSparseQR() : m_analysisIsok(false), m_factorizationIsok(false) 76 | { } 77 | 78 | /** Construct a QR factorization of the matrix \a mat. 79 | * 80 | * \sa compute() 81 | */ 82 | explicit BlockDiagonalSparseQR(const MatrixType& mat) : m_analysisIsok(false), m_factorizationIsok(false) 83 | { 84 | compute(mat); 85 | } 86 | 87 | /** Computes the QR factorization of the sparse matrix \a mat. 88 | * 89 | * 90 | * If input pattern analysis has been successfully performed before, it won't be run again by default. 91 | * forcePatternAnalysis - if true, forces reruning pattern analysis of the input matrix 92 | * \sa analyzePattern(), factorize() 93 | */ 94 | void compute(const MatrixType& mat, const PermutationType &rowPerm = PermutationType(), bool forcePatternAlaysis = false) 95 | { 96 | analyzePattern(mat, rowPerm); 97 | 98 | // Reset variables before the factorization 99 | m_isInitialized = false; 100 | m_factorizationIsok = false; 101 | factorize(mat); 102 | } 103 | void analyzePattern(const MatrixType& mat, const PermutationType &rowPerm = PermutationType()); 104 | void factorize(const MatrixType& mat); 105 | 106 | /** \returns the number of rows of the represented matrix. 107 | */ 108 | inline Index rows() const { return m_R.rows(); } 109 | 110 | /** \returns the number of columns of the represented matrix. 111 | */ 112 | inline Index cols() const { return m_R.cols();} 113 | 114 | /** \returns a const reference to the \b sparse upper triangular matrix R of the QR factorization. 115 | * 116 | * The structure of R is typically very sparse and contains upper triangular blocks on the diagonal. 117 | * A typical pattern of R might look like: 118 | * 119 | * QFormat == FullQ 120 | * 121 | * X X X 122 | * X X 123 | * X 124 | * X X X 125 | * X X 126 | * X 127 | * X X X 128 | * X X 129 | * X 130 | * X X X 131 | * X X 132 | * X 133 | * 134 | * QFormat == BlockDiagonalQ 135 | * \warning Note that forming block diagonal Q, matrix R will not be upper triangular anymore (it will be upper triangular only up to a row permutation) 136 | * 137 | * X X X 138 | * X X 139 | * X 140 | * 141 | * 142 | * 143 | * X X X 144 | * X X 145 | * X 146 | * X X X 147 | * X X 148 | * X 149 | * 150 | * 151 | * X X X 152 | * X X 153 | * X 154 | * 155 | */ 156 | const MatrixRType& matrixR() const { return m_R; } 157 | 158 | /** \returns the number of non linearly dependent columns. 159 | * \note Will be always equal to number of columns (full rank) in this case because the solver is not rank-revealing. 160 | */ 161 | Index rank() const 162 | { 163 | eigen_assert(m_isInitialized && "The factorization should be called first, use compute()"); 164 | return m_nonzeropivots; 165 | } 166 | 167 | /** \returns a SparseMatrix holding the explicit representation of the matrix Q. 168 | * The common usage of this function is to apply it to a dense matrix or vector 169 | * \code 170 | * VectorXd B1, B2; 171 | * // Initialize B1 172 | * B2 = matrixQ() * B1; 173 | * \endcode 174 | * 175 | * To get a plain SparseMatrix representation of Q: 176 | * \code 177 | * SparseMatrix Q; 178 | * Q = SparseQR >(A).matrixQ(); 179 | * \endcode 180 | * 181 | * Internally, matrix Q is composed explicitly and stored as SparseMatrix for block diagonal matrices. The structure of the output Q 182 | * is known a-priori and is known to be extremely sparse, and it is therefore not expensive to form it explicitly. 183 | * 184 | * The typical structure of the matrix Q will contain two shifted diagonals. The first diagonal is the "economy" matrix Q and the second is the orthogonal complement. It will look something like this: 185 | * 186 | * QFormat == FullQ 187 | * X X X X X X X 188 | * X X X X X X X 189 | * X X X X X X X 190 | * X X X X X X X 191 | * X X X X X X X 192 | * X X X X X X X 193 | * X X X X X X X 194 | * X X X X X X X 195 | * X X X X X X X 196 | * X X X X X X X 197 | * X X X X X X X 198 | * X X X X X X X 199 | * X X X X X X X 200 | * X X X X X X X 201 | * X X X X X X X 202 | * X X X X X X X 203 | * X X X X X X X 204 | * X X X X X X X 205 | * X X X X X X X 206 | * X X X X X X X 207 | * X X X X X X X 208 | * 209 | * QFormat == BlockDiagonalQ 210 | * \warning Note that forming block diagonal Q, matrix R will not be upper triangular anymore (it will be upper triangular only up to a row permutation) 211 | * 212 | * X X X X X X X 213 | * X X X X X X X 214 | * X X X X X X X 215 | * X X X X X X X 216 | * X X X X X X X 217 | * X X X X X X X 218 | * X X X X X X X 219 | * X X X X X X X 220 | * X X X X X X X 221 | * X X X X X X X 222 | * X X X X X X X 223 | * X X X X X X X 224 | * X X X X X X X 225 | * X X X X X X X 226 | * X X X X X X X 227 | * X X X X X X X 228 | * X X X X X X X 229 | * X X X X X X X 230 | * X X X X X X X 231 | * X X X X X X X 232 | * X X X X X X X 233 | * 234 | */ 235 | MatrixQType matrixQ() const { 236 | return m_Q; 237 | } 238 | 239 | /** \returns a const reference to the column permutation P that was applied to A such that A*P = Q*R 240 | * Right now, this method is not rank revealing and column permutation is always identity. 241 | */ 242 | const PermutationType& colsPermutation() const 243 | { 244 | eigen_assert(m_isInitialized && "Decomposition is not initialized."); 245 | return m_outputPerm_c; 246 | } 247 | 248 | /** \returns a const reference to the row permutation P that was applied to A such that P*A = Q*R 249 | * Permutation that reorders the rows of the input matrix so that it has some block banded structure. 250 | */ 251 | const PermutationType& rowsPermutation() const { 252 | eigen_assert(m_isInitialized && "Decomposition is not initialized."); 253 | return m_rowPerm; 254 | } 255 | 256 | /** \internal */ 257 | template 258 | bool _solve_impl(const MatrixBase &B, MatrixBase &dest) const 259 | { 260 | eigen_assert(m_isInitialized && "The factorization should be called first, use compute()"); 261 | eigen_assert(this->rows() == B.rows() && "SparseQR::solve() : invalid number of rows in the right hand side matrix"); 262 | 263 | Index rank = this->rank(); 264 | 265 | // Compute Q^T * b; 266 | typename Dest::PlainObject y = this->matrixQ().transpose() * B; 267 | typename Dest::PlainObject b = y; 268 | 269 | // Solve with the triangular matrix R 270 | y.resize((std::max)(cols(),y.rows()),y.cols()); 271 | y.topRows(rank) = this->matrixR().topLeftCorner(rank, rank).template triangularView().solve(b.topRows(rank)); 272 | y.bottomRows(y.rows()-rank).setZero(); 273 | 274 | // Apply the column permutation 275 | if (colsPermutation().size()) dest = colsPermutation() * y.topRows(cols()); 276 | else dest = y.topRows(cols()); 277 | 278 | m_info = Success; 279 | return true; 280 | } 281 | 282 | /** \returns the solution X of \f$ A X = B \f$ using the current decomposition of A. 283 | * 284 | * \sa compute() 285 | */ 286 | template 287 | inline const Solve solve(const MatrixBase& B) const 288 | { 289 | eigen_assert(m_isInitialized && "The factorization should be called first, use compute()"); 290 | eigen_assert(this->rows() == B.rows() && "SparseQR::solve() : invalid number of rows in the right hand side matrix"); 291 | return Solve(*this, B.derived()); 292 | } 293 | template 294 | inline const Solve solve(const SparseMatrixBase& B) const 295 | { 296 | eigen_assert(m_isInitialized && "The factorization should be called first, use compute()"); 297 | eigen_assert(this->rows() == B.rows() && "SparseQR::solve() : invalid number of rows in the right hand side matrix"); 298 | return Solve(*this, B.derived()); 299 | } 300 | 301 | /** \brief Reports whether previous computation was successful. 302 | * 303 | * \returns \c Success if computation was successful, 304 | * \c NumericalIssue if the QR factorization reports a numerical problem 305 | * \c InvalidInput if the input matrix is invalid 306 | * 307 | * \sa iparm() 308 | */ 309 | ComputationInfo info() const 310 | { 311 | eigen_assert(m_isInitialized && "Decomposition is not initialized."); 312 | return m_info; 313 | } 314 | 315 | protected: 316 | mutable ComputationInfo m_info; 317 | 318 | MatrixRType m_R; // The triangular factor matrix 319 | MatrixQType m_Q; // The orthogonal reflectors 320 | ScalarVector m_hcoeffs; // The Householder coefficients 321 | 322 | PermutationType m_outputPerm_c; // The final column permutation 323 | PermutationType m_rowPerm; // Row permutation matrix, always identity as solver does not perform row permutations 324 | 325 | Index m_nonzeropivots; // Number of non zero pivots found 326 | 327 | bool m_analysisIsok; 328 | bool m_factorizationIsok; 329 | Index m_numNonZeroQ; 330 | 331 | /* 332 | * Structures filled during sparse matrix pattern analysis. 333 | */ 334 | BlockBandedMatrixInfo m_blockInfo; 335 | }; 336 | 337 | template 338 | struct SparseQRUtils::HasRowsPermutation> { 339 | static const bool value = true; 340 | }; 341 | 342 | /** \brief Preprocessing step of a QR factorization 343 | * 344 | * \warning The matrix \a mat must be in compressed mode (see SparseMatrix::makeCompressed()). 345 | * 346 | * In this step, matrix pattern analysis is performed based on the type of the block QR solver. 347 | * 348 | * (a) BlockQRSolver::RowsAtCompileTime != Dynamic && BlockQRSolver::ColsAtCompileTime != Dynamic 349 | * In this case, it is assumed that the matrix has a known block diagonal structure with 350 | * constant block size and no overlaps. 351 | * 352 | * This step assumes that the user knows the structure beforehand and can specify it 353 | * in form of input parameters. 354 | * If called, the block diagonal pattern is automatically computed from the user input 355 | * and there is no need to call analyzePattern later on, and some unnecessary analysis 356 | * can be saved. 357 | * 358 | * An example of such block diagonal structure for a matrix could be: 359 | * (in this example we could assume having blocks 7x2) 360 | * 361 | * X X 362 | * X X 363 | * X X 364 | * X X 365 | * X X 366 | * X X 367 | * X X 368 | * X X 369 | * X X 370 | * X X 371 | * X X 372 | * X X 373 | * X X 374 | * X X 375 | * X X 376 | * X X 377 | * X X 378 | * X X 379 | * X X 380 | * X X 381 | * X X 382 | * 383 | * (b) BlockQRSolver::RowsAtCompileTime == Dynamic && BlockQRSolver::ColsAtCompileTime == Dynamic 384 | * In this case, row-reordering permutation of A is computed and matrix banded structure is analyzed. 385 | * This is neccessary preprocessing step before the matrix factorization is carried out. 386 | * 387 | * This step assumes there is some sort of banded structure in the matrix. 388 | * 389 | * \note In this step it is assumed that there is no empty row in the matrix \a mat. 390 | */ 391 | template 392 | void BlockDiagonalSparseQR::analyzePattern(const MatrixType& mat, const PermutationType &rowPerm) { 393 | // Save possible row permutation 394 | if (rowPerm.rows() == 0) { 395 | // If there's no row permutation given, set it to identity 396 | m_rowPerm.setIdentity(mat.rows()); 397 | } else { 398 | // Otherwise use the given row permutation 399 | m_rowPerm = rowPerm; 400 | } 401 | // No need to analyze the matrix, it's already block diagonal 402 | m_R.resize(mat.rows(), mat.cols()); 403 | 404 | m_analysisIsok = true; 405 | } 406 | 407 | /** \brief Performs the numerical QR factorization of the input matrix 408 | * 409 | * The function SparseQR::analyzePattern(const MatrixType&) must have been called beforehand with 410 | * a matrix having the same sparsity pattern than \a mat. 411 | * 412 | * \param mat The sparse column-major matrix 413 | */ 414 | template 415 | void BlockDiagonalSparseQR::factorize(const MatrixType& mat) { 416 | // Initialize the matrix for column permutations (will be filled during the factorization) 417 | m_outputPerm_c.setIdentity(mat.cols()); 418 | 419 | Index numBlocks = mat.size(); 420 | Index rank = 0; 421 | 422 | // Q is rows x rows, R is rows x cols 423 | BlockMatrixType block = mat[0]; 424 | std::vector > tripletsR(numBlocks * block.rows() * block.cols()); 425 | m_Q.resize(mat.rows(), mat.rows()); 426 | m_Q.reserve(this->m_blockInfo.nonZeroQEstimate); 427 | 428 | Index m1 = 0; 429 | Index N_start = mat.cols(); 430 | Index base_row = 0; 431 | Index base_col = 0; 432 | for (int i = 0; i < numBlocks; i++) { 433 | // Copy current block 434 | block = mat[i]; 435 | 436 | // Perform QR 437 | BlockQRSolver blockSolver; 438 | blockSolver.compute(block); 439 | //rank += blockSolver.rank(); 440 | rank += blockSolver.cols(); 441 | // FixMe: jasvob - only inner block is rank revealing for now. 442 | // The zero columns of each block are not shifted to the end. 443 | // Therefore the method as a whole is not and ... 444 | // ... we cannot treat the result by viewing only first "rank" columns 445 | 446 | typename BlockQRSolver::MatrixQType Qi = blockSolver.matrixQ(); 447 | typename BlockQRSolver::MatrixRType Ri = blockSolver.matrixR(); 448 | 449 | // Assemble into final Q 450 | if (block.rows() >= block.cols()) { 451 | // each rectangular Qi is partitioned into [U N] where U is rxc and N is rx(r-c) 452 | // All the Us are gathered in the leftmost nc columns of Q, all Ns to the right 453 | 454 | // Q 455 | if(QFormat == MatrixQFormat::FullQ) { 456 | Index curr_m1 = (block.rows() - block.cols()); 457 | for (Index j = 0; j < block.rows(); j++) { 458 | assert(base_row + j < m_Q.rows()); 459 | m_Q.startVec(base_row + j); 460 | // Us 461 | for (Index k = 0; k < block.cols(); k++) { 462 | assert(base_col + k < m_Q.cols()); 463 | m_Q.insertBack(base_row + j, base_col + k) = Qi.coeff(j, k); 464 | } 465 | // Ns 466 | for (Index k = 0; k < curr_m1; k++) { 467 | assert(N_start + m1 + k < m_Q.cols()); 468 | m_Q.insertBack(base_row + j, N_start + m1 + k) = Qi.coeff(j, block.cols() + k); 469 | } 470 | } 471 | m1 += curr_m1; 472 | 473 | // R 474 | // Only the top cxc of R is nonzero, so c rows at a time 475 | for (Index j = 0; j < block.cols(); j++) { 476 | for (Index k = j; k < block.cols(); k++) { 477 | tripletsR.emplace_back(base_col + j, base_col + k, Ri.coeff(j, k)); 478 | } 479 | } 480 | } else if(QFormat == MatrixQFormat::BlockDiagonalQ) { 481 | // Q diag 482 | Index curr_m1 = (block.rows() - block.cols()); 483 | for (Index j = 0; j < block.rows(); j++) { 484 | assert(base_row + j < m_Q.rows()); 485 | m_Q.startVec(base_row + j); 486 | // Us 487 | for (Index k = 0; k < block.rows(); k++) { 488 | assert(base_col + k < m_Q.cols()); 489 | m_Q.insertBack(base_row + j, base_row + k) = Qi.coeff(j, k); 490 | } 491 | } 492 | m1 += curr_m1; 493 | 494 | // R 495 | // Only the top cxc of R is nonzero, so c rows at a time 496 | for (Index j = 0; j < block.cols(); j++) { 497 | for (Index k = j; k < block.cols(); k++) { 498 | tripletsR.emplace_back(base_row + j, base_col + k, Ri.coeff(j, k)); 499 | } 500 | } 501 | } else { 502 | // Non-existing format requested? 503 | eigen_assert(false && "Block Diagonal QR decomposition ... non-existing format of matrix Q requested!"); 504 | m_info = InvalidInput; 505 | return; 506 | } 507 | 508 | } 509 | else { 510 | // Just concatenate everything -- it's upper triangular anyway (although not rank-revealing... xxfixme with colperm?) 511 | // xx and indeed for landscape, don't even need to compute QR after we've done the leftmost #rows columns 512 | 513 | eigen_assert(false && "Block Diagonal QR decomposition ... case mat.cols() > mat.rows() not implemented!"); 514 | m_info = InvalidInput; 515 | return; 516 | } 517 | 518 | // fill cols permutation 519 | for (Index j = 0; j < block.cols(); j++) { 520 | m_outputPerm_c.indices()(base_col + j, 0) = base_col + blockSolver.colsPermutation().indices()(j, 0); 521 | } 522 | 523 | // Update base row and base col 524 | base_row += block.rows(); 525 | base_col += block.cols(); 526 | } 527 | // Check last bi and if it doesn't reach until the last row of input matrix, it means there's a 0 block below 528 | // That will mean zeros in R and so no change needed 529 | // It will mean identity in Q -> add it 530 | for (int i = base_row; i < mat.rows(); i++) { 531 | m_Q.startVec(i); 532 | m_Q.insertBack(i, i) = Scalar(1.0); 533 | } 534 | 535 | // Now build Q and R from Qs and Rs of each block 536 | m_Q.finalize(); 537 | 538 | m_R.resize(mat.rows(), mat.cols()); 539 | m_R.setZero(); 540 | m_R.setFromTriplets(tripletsR.begin(), tripletsR.end()); 541 | m_R.makeCompressed(); 542 | 543 | m_nonzeropivots = rank; 544 | m_isInitialized = true; 545 | m_info = Success; 546 | m_factorizationIsok = true; 547 | } 548 | 549 | 550 | } // end namespace QRKit 551 | 552 | #endif 553 | -------------------------------------------------------------------------------- /src/QRKit/BlockMatrix1x2.h: -------------------------------------------------------------------------------- 1 | // This file is part of Eigen, a lightweight C++ template library 2 | // for linear algebra. 3 | // 4 | // Copyright (C) 2017 Jan Svoboda 5 | // Copyright (C) 2016 Andrew Fitzgibbon 6 | // 7 | // This Source Code Form is subject to the terms of the Mozilla 8 | // Public License v. 2.0. If a copy of the MPL was not distributed 9 | // with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 10 | 11 | #ifndef BLOCK_MATRIX_1X2_H 12 | #define BLOCK_MATRIX_1X2_H 13 | 14 | namespace QRKit { 15 | /* 16 | * Container for storing 2x1 block matrices. 17 | * Such matrices are composed from two subblocks of arbitrary types specified as template arguments. 18 | * 19 | * An example would be block angular matrix, which typically contains a very 20 | * sparse left block and dense (or almost dense) right block. An example: 21 | * 22 | * XXX XXX 23 | * XXX XXX 24 | * XXX XXX 25 | * XXX XXX 26 | * XXX XXX 27 | * XXX XXX 28 | * XXXXXX 29 | * 30 | */ 31 | template 32 | class BlockMatrix1x2 { 33 | public: 34 | BlockMatrix1x2(LeftBlockMatrixType &leftBlock, RightBlockMatrixType &rightBlock) 35 | : leftBlockNat(leftBlock), rightBlockMat(rightBlock) { 36 | 37 | eigen_assert(leftBlockNat.rows() == rightBlockMat.rows()); 38 | } 39 | 40 | RightBlockMatrixType& rightBlock() { 41 | return this->rightBlockMat; 42 | } 43 | 44 | LeftBlockMatrixType& leftBlock() { 45 | return this->leftBlockNat; 46 | } 47 | 48 | const RightBlockMatrixType& rightBlock() const { 49 | return this->rightBlockMat; 50 | } 51 | 52 | const LeftBlockMatrixType& leftBlock() const { 53 | return this->leftBlockNat; 54 | } 55 | 56 | int rows() const { 57 | return this->leftBlockNat.rows(); 58 | } 59 | 60 | int cols() const { 61 | return this->leftBlockNat.cols() + this->rightBlockMat.cols(); 62 | } 63 | 64 | protected: 65 | RightBlockMatrixType &rightBlockMat; 66 | LeftBlockMatrixType &leftBlockNat; 67 | }; 68 | } 69 | 70 | #endif 71 | 72 | -------------------------------------------------------------------------------- /src/QRKit/BlockYTY.h: -------------------------------------------------------------------------------- 1 | // This file is part of Eigen, a lightweight C++ template library 2 | // for linear algebra. 3 | // 4 | // Copyright (C) 2017 Jan Svoboda 5 | // Copyright (C) 2016 Andrew Fitzgibbon 6 | // 7 | // This Source Code Form is subject to the terms of the Mozilla 8 | // Public License v. 2.0. If a copy of the MPL was not distributed 9 | // with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 10 | 11 | #ifndef BLOCK_YTY_H 12 | #define BLOCK_YTY_H 13 | 14 | /* 15 | * A dense block of the compressed WY representation (YTY) of the Householder product. 16 | * Stores matrices Y (m x n) and T (n x n) and number of zeros between main diagonal and subdiagonal parts of the block YTY. 17 | * Provides overloaded multiplication operator (*) allowing to easily perform the multiplication with a dense vector (Y * (T * (Y' * v))) 18 | */ 19 | namespace QRKit { 20 | template class BlockYTY; 21 | template struct BlockYTYProductReturnType; 22 | template struct BlockYTYProductTransposeReturnType; 23 | template struct BlockYTY_VecProduct; 24 | } 25 | 26 | namespace Eigen { 27 | namespace internal { 28 | 29 | // traits 30 | template struct traits > 31 | { 32 | typedef typename BlockYTYType::MatrixType ReturnType; 33 | typedef typename ReturnType::StorageIndex StorageIndex; 34 | typedef typename ReturnType::StorageKind StorageKind; 35 | enum { 36 | RowsAtCompileTime = Dynamic, 37 | ColsAtCompileTime = Dynamic 38 | }; 39 | }; 40 | 41 | template struct traits > 42 | { 43 | typedef typename BlockYTYType::MatrixType ReturnType; 44 | }; 45 | 46 | template struct traits > 47 | { 48 | typedef typename Derived::PlainObject ReturnType; 49 | }; 50 | 51 | // BlockYTY_traits 52 | template struct BlockYTY_traits { }; 53 | 54 | } // End namespace internal 55 | } // End namespace Eigen 56 | 57 | namespace QRKit { 58 | template 59 | class BlockYTYTranspose { 60 | public: 61 | typedef typename BlockYTY::VectorType VectorType; 62 | 63 | BlockYTYTranspose() : yty(NULL) { 64 | } 65 | 66 | BlockYTYTranspose(BlockYTY* blk) : yty(blk) { 67 | } 68 | 69 | VectorType operator*(const VectorType& other) const { 70 | return (yty->Y() * (yty->T().transpose() * (yty->Y().transpose() * other))); 71 | } 72 | 73 | private: 74 | BlockYTY* yty; 75 | }; 76 | 77 | template 78 | class BlockYTY { 79 | public: 80 | typedef Eigen::Matrix MatrixType; 81 | typedef ScalarType Scalar; 82 | typedef Eigen::Matrix VectorType; 83 | 84 | BlockYTY() { 85 | } 86 | 87 | BlockYTY(const MatrixType& Y, const MatrixType& T, const IndexType row, const IndexType col, const IndexType numZeros) 88 | : matY(Y), matT(T), ridx(row), cidx(col), nzrs(numZeros) { 89 | 90 | } 91 | 92 | const MatrixType& Y() const { 93 | return this->matY; 94 | } 95 | 96 | const MatrixType& T() const { 97 | return this->matT; 98 | } 99 | 100 | IndexType rowIndex() const { 101 | return this->ridx; 102 | } 103 | IndexType colIndex() const { 104 | return this->cidx; 105 | } 106 | 107 | IndexType rows() const { 108 | return this->matY.rows(); 109 | } 110 | IndexType cols() const { 111 | return this->matY.cols(); 112 | } 113 | 114 | IndexType numZeros() const { 115 | return this->nzrs; 116 | } 117 | 118 | BlockYTYTranspose> transpose() const { 119 | return BlockYTYTranspose>(this); 120 | } 121 | 122 | VectorType operator*(const VectorType& other) const { 123 | return (this->matY * (this->matT * (this->matY.transpose() * other))); 124 | } 125 | 126 | BlockYTYProductReturnType yty() const { 127 | return BlockYTYProductReturnType(*this); 128 | } 129 | 130 | protected: 131 | MatrixType matY; 132 | MatrixType matT; 133 | 134 | IndexType ridx; 135 | IndexType cidx; 136 | IndexType nzrs; 137 | 138 | template friend struct BlockYTY_VecProduct; 139 | }; 140 | 141 | /************************************ Expression templates for the evaluation of single YTY product ***************************************/ 142 | template 143 | struct BlockYTY_VecProduct : Eigen::ReturnByValue > 144 | { 145 | // Get the references 146 | BlockYTY_VecProduct(const BlockYTYType& yty, const Derived& other, bool transpose) : 147 | m_yty(yty), m_other(other), m_transpose(transpose) {} 148 | inline Index rows() const { return m_other.rows(); } 149 | inline Index cols() const { return m_other.cols(); } 150 | 151 | // Assign to a vector 152 | template 153 | void evalTo(DesType& res) const 154 | { 155 | res = m_other; 156 | 157 | Derived segmentVec; 158 | SparseQRUtils::SegmentDescriptors segDescs = { 159 | { m_yty.rowIndex(), m_yty.cols() }, 160 | { m_yty.rowIndex() + m_yty.cols() + m_yty.numZeros(), m_yty.rows() - m_yty.cols() } 161 | }; 162 | SparseQRUtils::getVectorSegments(segmentVec, res, segDescs, m_yty.rows()); 163 | if (m_transpose) { 164 | // Non-aliasing expr -> noalias() 165 | segmentVec.noalias() += (m_yty.matY * (m_yty.matT.transpose() * (m_yty.matY.transpose() * segmentVec))); 166 | } 167 | else { 168 | // Non-aliasing expr -> noalias() 169 | segmentVec.noalias() += (m_yty.matY * (m_yty.matT * (m_yty.matY.transpose() * segmentVec))); 170 | } 171 | SparseQRUtils::setVectorSegments(res, segmentVec, segDescs); 172 | } 173 | 174 | const BlockYTYType& m_yty; 175 | const Derived& m_other; 176 | bool m_transpose; 177 | }; 178 | 179 | template 180 | struct BlockYTYProductReturnType : public EigenBase > 181 | { 182 | typedef typename BlockYTYType::Scalar Scalar; 183 | typedef typename BlockYTYType::MatrixType DenseMatrix; 184 | enum { 185 | RowsAtCompileTime = Dynamic, 186 | ColsAtCompileTime = Dynamic 187 | }; 188 | explicit BlockYTYProductReturnType(const BlockYTYType& yty) : m_yty(yty) {} 189 | 190 | template 191 | BlockYTY_VecProduct operator*(const MatrixBase& other) 192 | { 193 | return BlockYTY_VecProduct(m_yty, other.derived(), false); 194 | } 195 | template 196 | BlockYTY_VecProduct> operator*(const SparseMatrix<_Scalar, _Options, _Index>& other) 197 | { 198 | return BlockYTY_VecProduct>(m_yty, other, false); 199 | } 200 | BlockYTYProductTransposeReturnType adjoint() const 201 | { 202 | return BlockYTYProductTransposeReturnType(m_yty); 203 | } 204 | BlockYTYProductTransposeReturnType transpose() const 205 | { 206 | return BlockYTYProductTransposeReturnType(m_yty); 207 | } 208 | 209 | const BlockYTYType& m_yty; 210 | }; 211 | 212 | template 213 | struct BlockYTYProductTransposeReturnType 214 | { 215 | explicit BlockYTYProductTransposeReturnType(const BlockYTYType& yty) : m_yty(yty) {} 216 | template 217 | BlockYTY_VecProduct operator*(const MatrixBase& other) 218 | { 219 | return BlockYTY_VecProduct(m_yty, other.derived(), true); 220 | } 221 | template 222 | BlockYTY_VecProduct> operator*(const SparseMatrix<_Scalar, _Options, _Index>& other) 223 | { 224 | return BlockYTY_VecProduct>(m_yty, other, true); 225 | } 226 | const BlockYTYType& m_yty; 227 | }; 228 | } // End namespace QRKit 229 | 230 | namespace Eigen { 231 | namespace internal { 232 | template 233 | struct evaluator_traits > 234 | { 235 | typedef typename BlockYTYType::MatrixType MatrixType; 236 | typedef typename storage_kind_to_evaluator_kind::Kind Kind; 237 | typedef SparseShape Shape; 238 | }; 239 | } 240 | /************************************************************************************************************************************/ 241 | } 242 | 243 | #endif 244 | 245 | -------------------------------------------------------------------------------- /src/QRKit/BlockedThinDenseQR.h: -------------------------------------------------------------------------------- 1 | // This file is part of Eigen, a lightweight C++ template library 2 | // for linear algebra. 3 | // 4 | // Copyright (C) 2017 Jan Svoboda 5 | // Copyright (C) 2016 Andrew Fitzgibbon 6 | // 7 | // This Source Code Form is subject to the terms of the Mozilla 8 | // Public License v. 2.0. If a copy of the MPL was not distributed 9 | // with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 10 | 11 | #ifndef QRKIT_BLOCKED_THIN_DENSE_QR_H 12 | #define QRKIT_BLOCKED_THIN_DENSE_QR_H 13 | 14 | #include 15 | #include 16 | #include 17 | #include "BlockedThinQRBase.h" 18 | 19 | namespace QRKit { 20 | template class BlockedThinDenseQR; 21 | } 22 | 23 | namespace Eigen { 24 | namespace internal { 25 | // BlockedThinDenseQR_traits 26 | template struct BlockedThinDenseQR_traits { }; 27 | template struct BlockedThinDenseQR_traits> { 28 | typedef Matrix Vector; 29 | }; 30 | template struct BlockedThinDenseQR_traits> { 31 | typedef SparseVector Vector; 32 | }; 33 | } // End namespace internal 34 | } // End namespace Eigen 35 | 36 | namespace QRKit { 37 | /** 38 | * \ingroup BlockedThinDenseQR_Module 39 | * \class BlockedThinDenseQR 40 | * \brief Sparse Householder QR Factorization for banded matrices operating on dense matrix. 41 | * 42 | * In some cases, it is faster to represent the input matrix as dense matrix and treat its sparsity pattern internally. 43 | * Obviously, such approach requires more memory, it is however computationally much more efficient. 44 | * 45 | * This implementation is not rank revealing and uses Eigen::HouseholderQR for solving the dense blocks. 46 | * 47 | * Q is the orthogonal matrix represented as products of Householder reflectors. 48 | * Use matrixQ() to get an expression and matrixQ().transpose() to get the transpose. 49 | * You can then apply it to a vector. 50 | * 51 | * R is the sparse triangular or trapezoidal matrix. The later occurs when A is rank-deficient. 52 | * matrixR().topLeftCorner(rank(), rank()) always returns a triangular factor of full rank. 53 | * 54 | * \tparam _MatrixType The type of the sparse matrix A, must be a column-major SparseMatrix<> 55 | * \implsparsesolverconcept 56 | * 57 | * \warning The input sparse matrix A must be in compressed mode (see SparseMatrix::makeCompressed()). 58 | * 59 | */ 60 | 61 | template 62 | class BlockedThinDenseQR : public BlockedThinQRBase<_MatrixType, Eigen::HouseholderQR>, 63 | Eigen::Matrix, _SuggestedBlockCols> 64 | { 65 | protected: 66 | typedef BlockedThinQRBase<_MatrixType, Eigen::HouseholderQR>, 67 | Eigen::Matrix, _SuggestedBlockCols> Base; 68 | 69 | public: 70 | typedef _MatrixType MatrixType; 71 | typedef typename Base::Scalar Scalar; 72 | typedef typename Base::RealScalar RealScalar; 73 | typedef typename Base::StorageIndex StorageIndex; 74 | typedef typename Base::IndexVector IndexVector; 75 | typedef typename Base::DenseVectorType DenseVectorType; 76 | typedef typename Base::DenseMatrixType DenseMatrixType; 77 | 78 | typedef typename Base::MatrixQType MatrixQType; 79 | typedef typename Base::MatrixRType MatrixRType; 80 | typedef typename Base::PermutationType PermutationType; 81 | 82 | typedef typename Base::DenseBlockQR DenseBlockQR; 83 | 84 | typedef typename Base::BlockBandedMatrixInfo BlockBandedMatrixInfo; 85 | 86 | enum { 87 | ColsAtCompileTime = MatrixType::ColsAtCompileTime, 88 | MaxColsAtCompileTime = MatrixType::MaxColsAtCompileTime 89 | }; 90 | 91 | public: 92 | BlockedThinDenseQR() : Base() { 93 | } 94 | 95 | explicit BlockedThinDenseQR(const MatrixType& mat) : Base(mat) { 96 | } 97 | 98 | /** Computes the QR factorization of the sparse matrix \a mat. 99 | * 100 | * \warning The matrix \a mat must be in compressed mode (see SparseMatrix::makeCompressed()). 101 | * 102 | * \sa analyzePattern(), factorize() 103 | */ 104 | virtual void compute(const MatrixType& mat) 105 | { 106 | // Reset variables in case this method is called multiple times 107 | this->m_isInitialized = false; 108 | this->m_factorizationIsok = false; 109 | this->m_blocksYT.clear(); 110 | 111 | // Analyze input matrix pattern and perform row and column permutations 112 | // Stores input matrix to m_pmat 113 | analyzePattern(mat); 114 | 115 | // Create dense version of the already permuted input matrix 116 | // It is much faster to do the permutations on the sparse version 117 | this->m_R = mat; 118 | 119 | // And start factorizing block-by-block 120 | Index solvedCols = 0; 121 | Index cntr = 0; 122 | // As long as there are some unsolved columns 123 | while (solvedCols < this->m_R.cols()) { 124 | // Get next block info 125 | this->updateBlockInfo(solvedCols, this->m_R, 0, _SuggestedBlockCols); 126 | 127 | // Factorize current block 128 | factorize(this->m_R); 129 | solvedCols += this->denseBlockInfo.numCols; 130 | } 131 | 132 | this->m_nonzeroPivots = this->m_R.cols(); 133 | this->m_isInitialized = true; 134 | this->m_info = Success; 135 | } 136 | virtual void analyzePattern(const MatrixType& mat, bool rowPerm = true, bool colPerm = true) { 137 | // No column permutation here 138 | this->m_outputPerm_c.setIdentity(mat.cols()); 139 | 140 | // And no row permutatio neither 141 | this->m_rowPerm.setIdentity(mat.rows()); 142 | 143 | this->m_analysisIsok = true; 144 | } 145 | 146 | virtual void updateBlockInfo(const Index solvedCols, const MatrixType& mat, const Index newPivots, const Index blockCols = -1) { 147 | Index newCols = (blockCols > 0) ? blockCols : _SuggestedBlockCols; 148 | Index colIdx = solvedCols + newCols; 149 | Index numRows = mat.rows() - solvedCols; 150 | if (colIdx >= mat.cols()) { 151 | colIdx = mat.cols() - 1; 152 | newCols = mat.cols() - solvedCols; 153 | numRows = mat.rows() - solvedCols; 154 | } 155 | 156 | this->denseBlockInfo = typename BlockBandedMatrixInfo::MatrixBlockInfo(solvedCols, numRows, newCols); 157 | } 158 | 159 | virtual void factorize(DenseMatrixType& mat) { 160 | // Dense QR solver used for each dense block 161 | DenseBlockQR houseqr; 162 | 163 | /*********** Process the block ***********/ 164 | // 1) Factorize the block 165 | houseqr.compute(mat.block(this->denseBlockInfo.idxRow, this->denseBlockInfo.idxRow, this->denseBlockInfo.numRows, this->denseBlockInfo.numCols)); 166 | 167 | // 2) Create matrices Y and T 168 | DenseMatrixType Y, T; 169 | this->computeBlockedRepresentation(houseqr, Y, T); 170 | 171 | // Save current Y and T. The block YTY contains a main diagonal and subdiagonal part separated by (numZeros) zero rows. 172 | this->m_blocksYT.insert(typename SparseBlockYTY::Element(this->denseBlockInfo.idxRow, this->denseBlockInfo.idxRow, BlockYTY(Y, T, this->denseBlockInfo.idxRow, this->denseBlockInfo.idxCol, 0))); 173 | 174 | // Update the trailing columns of the matrix block 175 | this->updateMat(this->denseBlockInfo.idxRow, mat.cols(), mat, this->m_blocksYT.size() - 1); 176 | } 177 | 178 | protected: 179 | }; 180 | 181 | } // end namespace QRKit 182 | 183 | #endif 184 | -------------------------------------------------------------------------------- /src/QRKit/BlockedThinQRBase.h: -------------------------------------------------------------------------------- 1 | // This file is part of Eigen, a lightweight C++ template library 2 | // for linear algebra. 3 | // 4 | // Copyright (C) 2017 Jan Svoboda 5 | // Copyright (C) 2016 Andrew Fitzgibbon 6 | // 7 | // This Source Code Form is subject to the terms of the Mozilla 8 | // Public License v. 2.0. If a copy of the MPL was not distributed 9 | // with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 10 | 11 | #ifndef QRKIT_BLOCKED_THIN_QR_BASE_H 12 | #define QRKIT_BLOCKED_THIN_QR_BASE_H 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include "SparseBlockYTY.h" 19 | #include "SparseQRUtils.h" 20 | #include "SparseQROrdering.h" 21 | 22 | namespace QRKit{ 23 | template class BlockedThinQRBase; 24 | template struct BlockedThinQRBaseMatrixQReturnType; 25 | template struct BlockedThinQRBaseMatrixQTransposeReturnType; 26 | template struct BlockedThinQRBase_QProduct; 27 | } 28 | 29 | namespace Eigen { 30 | 31 | namespace internal { 32 | 33 | // traits 34 | template struct traits > 35 | { 36 | typedef typename BlockedThinQRBaseType::MatrixType ReturnType; 37 | typedef typename ReturnType::StorageIndex StorageIndex; 38 | typedef typename ReturnType::StorageKind StorageKind; 39 | enum { 40 | RowsAtCompileTime = Dynamic, 41 | ColsAtCompileTime = Dynamic 42 | }; 43 | }; 44 | 45 | template struct traits > 46 | { 47 | typedef typename BlockedThinQRBaseType::MatrixType ReturnType; 48 | }; 49 | 50 | template struct traits > 51 | { 52 | typedef typename Derived::PlainObject ReturnType; 53 | }; 54 | 55 | // BlockedThinQRBase_traits 56 | template struct BlockedThinQRBase_traits { }; 57 | template struct BlockedThinQRBase_traits> { 58 | typedef Matrix Vector; 59 | }; 60 | template struct BlockedThinQRBase_traits> { 61 | typedef SparseVector Vector; 62 | }; 63 | } // End namespace internal 64 | } // End namespace Eigen 65 | 66 | namespace QRKit { 67 | /** 68 | * \ingroup BlockedThinQRBase_Module 69 | * \class BlockedThinQRBase 70 | * \brief Sparse Householder QR Factorization for banded matrices 71 | * This implementation is not rank revealing and uses Eigen::HouseholderQR for solving the dense blocks. 72 | * 73 | * Q is the orthogonal matrix represented as products of Householder reflectors. 74 | * Use matrixQ() to get an expression and matrixQ().transpose() to get the transpose. 75 | * You can then apply it to a vector. 76 | * 77 | * R is the sparse triangular or trapezoidal matrix. The later occurs when A is rank-deficient. 78 | * matrixR().topLeftCorner(rank(), rank()) always returns a triangular factor of full rank. 79 | * 80 | * \tparam _MatrixType The type of the sparse matrix A, must be a column-major SparseMatrix<> 81 | * \implsparsesolverconcept 82 | * 83 | * \warning The input sparse matrix A must be in compressed mode (see SparseMatrix::makeCompressed()). 84 | * 85 | */ 86 | 87 | template 88 | struct SparseQRUtils::HasRowsPermutation> { 89 | static const bool value = true; 90 | }; 91 | 92 | template 93 | class BlockedThinQRBase : public SparseSolverBase > 94 | { 95 | protected: 96 | typedef SparseSolverBase > Base; 97 | using Base::m_isInitialized; 98 | 99 | public: 100 | using Base::_solve_impl; 101 | typedef _MatrixType MatrixType; 102 | typedef typename MatrixType::Scalar Scalar; 103 | typedef typename MatrixType::RealScalar RealScalar; 104 | typedef typename MatrixType::StorageIndex StorageIndex; 105 | typedef Matrix IndexVector; 106 | typedef Matrix DenseVectorType; 107 | typedef Matrix DenseMatrixType; 108 | 109 | typedef BlockedThinQRBaseMatrixQReturnType MatrixQType; 110 | typedef _MatrixRType MatrixRType; 111 | typedef PermutationMatrix PermutationType; 112 | 113 | typedef _DenseBlockQR DenseBlockQR; 114 | 115 | /* 116 | * Stores information about a dense block in a block sparse matrix. 117 | * Holds the position of the block (row index, column index) and its size (number of rows, number of columns). 118 | */ 119 | typedef typename SparseQRUtils::BlockBandedMatrixInfo BlockBandedMatrixInfo; 120 | 121 | enum { 122 | ColsAtCompileTime = MatrixType::ColsAtCompileTime, 123 | MaxColsAtCompileTime = MatrixType::MaxColsAtCompileTime 124 | }; 125 | 126 | public: 127 | BlockedThinQRBase() : m_analysisIsok(false) 128 | { } 129 | 130 | /** Construct a QR factorization of the matrix \a mat. 131 | * 132 | * \warning The matrix \a mat must be in compressed mode (see SparseMatrix::makeCompressed()). 133 | * 134 | * \sa compute() 135 | */ 136 | explicit BlockedThinQRBase(const MatrixType& mat) : m_analysisIsok(false) 137 | { 138 | compute(mat); 139 | } 140 | 141 | /** Computes the QR factorization of the sparse matrix \a mat. 142 | * 143 | * \warning The matrix \a mat must be in compressed mode (see SparseMatrix::makeCompressed()). 144 | * 145 | * If input pattern analysis has been successfully performed before, it won't be run again by default. 146 | * forcePatternAnalysis - if true, forces reruning pattern analysis of the input matrix 147 | * \sa analyzePattern(), factorize() 148 | */ 149 | virtual void compute(const MatrixType& mat) = 0; 150 | virtual void analyzePattern(const MatrixType& mat, bool rowPerm = true, bool colPerm = true) = 0; 151 | virtual void updateBlockInfo(const Index solvedCols, const MatrixType& mat, const Index newPivots, const Index blockCols = -1) = 0; 152 | virtual void factorize(DenseMatrixType& mat) = 0; 153 | 154 | /** \returns the number of rows of the represented matrix. 155 | */ 156 | inline Index rows() const { return m_R.rows(); } 157 | 158 | /** \returns the number of columns of the represented matrix. 159 | */ 160 | inline Index cols() const { return m_R.cols(); } 161 | 162 | /** \returns a const reference to the \b sparse upper triangular matrix R of the QR factorization. 163 | * \warning The entries of the returned matrix are not sorted. This means that using it in algorithms 164 | * expecting sorted entries will fail. This include random coefficient accesses (SpaseMatrix::coeff()), 165 | * and coefficient-wise operations. Matrix products and triangular solves are fine though. 166 | * 167 | * To sort the entries, you can assign it to a row-major matrix, and if a column-major matrix 168 | * is required, you can copy it again: 169 | * \code 170 | * SparseMatrix R = qr.matrixR(); // column-major, not sorted! 171 | * SparseMatrix Rr = qr.matrixR(); // row-major, sorted 172 | * SparseMatrix Rc = Rr; // column-major, sorted 173 | * \endcode 174 | */ 175 | const MatrixRType& matrixR() const { return m_R; } 176 | 177 | /** \returns the number of non linearly dependent columns as determined by the pivoting threshold. 178 | * 179 | * \sa setPivotThreshold() 180 | */ 181 | Index rank() const 182 | { 183 | eigen_assert(m_isInitialized && "The factorization should be called first, use compute()"); 184 | return m_nonzeroPivots; 185 | } 186 | 187 | /** \returns an expression of the matrix Q as products of sparse Householder reflectors. 188 | * The common usage of this function is to apply it to a dense matrix or vector 189 | * \code 190 | * VectorXd B1, B2; 191 | * // Initialize B1 192 | * B2 = matrixQ() * B1; 193 | * \endcode 194 | * 195 | * To get a plain SparseMatrix representation of Q: 196 | * \code 197 | * SparseMatrix Q; 198 | * Q = BlockedThinQRBase >(A).matrixQ(); 199 | * \endcode 200 | * Internally, this call simply performs a sparse product between the matrix Q 201 | * and a sparse identity matrix. However, due to the fact that the sparse 202 | * reflectors are stored unsorted, two transpositions are needed to sort 203 | * them before performing the product. 204 | */ 205 | BlockedThinQRBaseMatrixQReturnType matrixQ() const 206 | { 207 | return BlockedThinQRBaseMatrixQReturnType(*this); 208 | } 209 | 210 | /** \returns a const reference to the column permutation P that was applied to A such that A*P = Q*R 211 | * It is the combination of the fill-in reducing permutation and numerical column pivoting. 212 | */ 213 | const PermutationType& colsPermutation() const 214 | { 215 | eigen_assert(m_isInitialized && "Decomposition is not initialized."); 216 | return m_outputPerm_c; 217 | } 218 | 219 | const PermutationType& rowsPermutation() const { 220 | eigen_assert(m_isInitialized && "Decomposition is not initialized."); 221 | return this->m_rowPerm; 222 | } 223 | 224 | /** \internal */ 225 | template 226 | bool _solve_impl(const MatrixBase& B, MatrixBase& dest) const 227 | { 228 | eigen_assert(m_isInitialized && "The factorization should be called first, use compute()"); 229 | eigen_assert(this->rows() == B.rows() && "BlockedThinQRBase::solve() : invalid number of rows in the right hand side matrix"); 230 | 231 | Index rank = this->rank(); 232 | 233 | // Compute Q^T * b; 234 | typename Dest::PlainObject y = this->matrixQ().transpose() * B; 235 | typename Dest::PlainObject b = y; 236 | 237 | // Solve with the triangular matrix R 238 | y.resize((std::max)(cols(), y.rows()), y.cols()); 239 | y.topRows(rank) = this->matrixR().topLeftCorner(rank, rank).template triangularView().solve(b.topRows(rank)); 240 | y.bottomRows(y.rows() - rank).setZero(); 241 | 242 | dest = y.topRows(cols()); 243 | 244 | m_info = Success; 245 | return true; 246 | } 247 | 248 | /** \returns the solution X of \f$ A X = B \f$ using the current decomposition of A. 249 | * 250 | * \sa compute() 251 | */ 252 | template 253 | inline const Solve solve(const MatrixBase& B) const 254 | { 255 | eigen_assert(m_isInitialized && "The factorization should be called first, use compute()"); 256 | eigen_assert(this->rows() == B.rows() && "BlockedThinQRBase::solve() : invalid number of rows in the right hand side matrix"); 257 | return Solve(*this, B.derived()); 258 | } 259 | template 260 | inline const Solve solve(const SparseMatrixBase& B) const 261 | { 262 | eigen_assert(m_isInitialized && "The factorization should be called first, use compute()"); 263 | eigen_assert(this->rows() == B.rows() && "BlockedThinQRBase::solve() : invalid number of rows in the right hand side matrix"); 264 | return Solve(*this, B.derived()); 265 | } 266 | 267 | /** \brief Reports whether previous computation was successful. 268 | * 269 | * \returns \c Success if computation was successful, 270 | * \c NumericalIssue if the QR factorization reports a numerical problem 271 | * \c InvalidInput if the input matrix is invalid 272 | * 273 | * \sa iparm() 274 | */ 275 | ComputationInfo info() const 276 | { 277 | eigen_assert(m_isInitialized && "Decomposition is not initialized."); 278 | return m_info; 279 | } 280 | 281 | protected: 282 | typedef SparseBlockYTY SparseBlockYTYType; 283 | 284 | mutable ComputationInfo m_info; 285 | 286 | MatrixRType m_R; // The triangular factor matrix 287 | SparseBlockYTYType m_blocksYT; // Sparse block matrix storage holding the dense YTY blocks of the blocked representation of Householder reflectors. 288 | 289 | PermutationType m_outputPerm_c; // The final column permutation (for compatibility here, set to identity) 290 | PermutationType m_rowPerm; 291 | 292 | Index m_nonzeroPivots; // Number of non zero pivots found 293 | 294 | bool m_analysisIsok; 295 | bool m_factorizationIsok; 296 | 297 | /* 298 | * Structures filled during sparse matrix pattern analysis. 299 | */ 300 | typename BlockBandedMatrixInfo::MatrixBlockInfo denseBlockInfo; 301 | 302 | template friend struct BlockedThinQRBase_QProduct; 303 | 304 | void updateMat(const Index& fromIdx, const Index& toIdx, DenseMatrixType& mat, const Index& blockK = -1); 305 | void computeBlockedRepresentation(const DenseBlockQR& slvr, DenseMatrixType& Y, DenseMatrixType& T); 306 | }; 307 | 308 | template 309 | void BlockedThinQRBase::updateMat(const Index& fromIdx, const Index& toIdx, DenseMatrixType& mat, const Index& blockK) { 310 | // Now update the unsolved rest of m_pmat 311 | Index blockRows = this->m_blocksYT[blockK].value.rows(); 312 | 313 | // loop over all items 314 | #pragma omp parallel for 315 | for (int j = fromIdx; j < toIdx; j++) { 316 | mat.middleRows(this->denseBlockInfo.idxRow, blockRows).col(j).noalias() 317 | += (this->m_blocksYT[blockK].value.Y() * (this->m_blocksYT[blockK].value.T().transpose() * (this->m_blocksYT[blockK].value.Y().transpose() * mat.middleRows(this->denseBlockInfo.idxRow, blockRows).col(j)))); 318 | } 319 | } 320 | 321 | template 322 | void BlockedThinQRBase::computeBlockedRepresentation(const DenseBlockQR& slvr, DenseMatrixType& Y, DenseMatrixType& T) { 323 | Index numRows = this->denseBlockInfo.numRows; 324 | Index numCols = this->denseBlockInfo.numCols; 325 | 326 | T = DenseMatrixType::Zero(numCols, numCols); 327 | Y = DenseMatrixType::Identity(numRows, numCols); 328 | for (int bc = 0; bc < numCols; bc++) { 329 | Y.col(bc).segment(bc + 1, numRows - bc - 1) = slvr.householderQ().essentialVector(bc); 330 | } 331 | Eigen::internal::make_block_householder_triangular_factor(T, Y, slvr.hCoeffs()); 332 | T = -T; 333 | } 334 | 335 | /* 336 | * General Householder product evaluation performing Q * A or Q.T * A. 337 | * The general version is assuming that A is sparse and that the output will be sparse as well. 338 | * Offers single-threaded and multi-threaded implementation. 339 | * The choice of implementation depends on a template parameter of the BlockedThinQRBase class. 340 | * The single-threaded implementation cannot work in-place. It is implemented this way for performance related reasons. 341 | */ 342 | template 343 | struct BlockedThinQRBase_QProduct : ReturnByValue > 344 | { 345 | typedef typename BlockedThinQRBaseType::Scalar Scalar; 346 | typedef SparseMatrix MatrixType; 347 | typedef typename BlockedThinQRBaseType::MatrixType DenseMatrixType; 348 | typedef typename BlockedThinQRBaseType::DenseVectorType DenseVectorType; 349 | 350 | typedef typename Eigen::internal::BlockedThinQRBase_traits::Vector SparseVector; 351 | 352 | typedef std::vector>> ResValsVector; 353 | 354 | // Get the references 355 | BlockedThinQRBase_QProduct(const BlockedThinQRBaseType& qr, const Derived& other, bool transpose) : 356 | m_qr(qr), m_other(other), m_transpose(transpose) {} 357 | inline Index rows() const { return m_transpose ? m_qr.rows() : m_qr.cols(); } 358 | inline Index cols() const { return m_other.cols(); } 359 | 360 | // Assign to a vector 361 | template 362 | void evalTo(DesType& res) const 363 | { 364 | Index m = m_qr.rows(); 365 | Index n = m_qr.cols(); 366 | 367 | Eigen::DynamicSparseMatrix tmp(m_other.rows(), m_other.cols()); 368 | Index numNonZeros = 0; 369 | 370 | #pragma omp parallel for 371 | // loop over all items 372 | for (int j = 0; j < m_other.cols(); j++) 373 | { 374 | DenseVectorType resColJd = m_other.col(j); 375 | 376 | if (m_transpose) { 377 | resColJd.noalias() = m_qr.m_blocksYT.sequenceYTY().transpose() * resColJd; 378 | } 379 | else { 380 | resColJd.noalias() = m_qr.m_blocksYT.sequenceYTY() * resColJd; 381 | } 382 | 383 | // Write the result back to j-th column of res 384 | SparseVector resColJ = resColJd.sparseView(); 385 | for (typename SparseVector::InnerIterator it(resColJ); it; ++it) { 386 | tmp.coeffRef(it.row(), j) = it.value(); 387 | } 388 | } 389 | res = tmp; 390 | 391 | // Don't forget to call finalize 392 | res.finalize(); 393 | } 394 | 395 | const BlockedThinQRBaseType& m_qr; 396 | const Derived& m_other; 397 | bool m_transpose; 398 | }; 399 | 400 | /* 401 | * Specialization of the Householder product evaluation performing Q * A or Q.T * A 402 | * for the case when A and the output are dense vectors.= 403 | * Offers only single-threaded implementation as the overhead of multithreading would not bring any speedup for a dense vector (A is single column). 404 | */ 405 | 406 | template 407 | struct BlockedThinQRBase_QProduct : ReturnByValue > 408 | { 409 | typedef typename BlockedThinQRBaseType::MatrixType MatrixType; 410 | typedef typename BlockedThinQRBaseType::Scalar Scalar; 411 | typedef typename BlockedThinQRBaseType::DenseVectorType DenseVectorType; 412 | 413 | // Get the references 414 | BlockedThinQRBase_QProduct(const BlockedThinQRBaseType& qr, const DenseVectorType& other, bool transpose) : 415 | m_qr(qr), m_other(other), m_transpose(transpose) {} 416 | inline Index rows() const { return m_transpose ? m_qr.rows() : m_qr.cols(); } 417 | inline Index cols() const { return m_other.cols(); } 418 | 419 | // Assign to a vector 420 | template 421 | void evalTo(DesType& res) const 422 | { 423 | Index m = m_qr.rows(); 424 | Index n = m_qr.cols(); 425 | res = m_other; 426 | 427 | //Compute res = Q' * other (other is vector - only one column => no iterations of j) 428 | if (m_transpose) { 429 | res.noalias() = m_qr.m_blocksYT.sequenceYTY().transpose() * res; 430 | } 431 | else { 432 | res.noalias() = m_qr.m_blocksYT.sequenceYTY() * res; 433 | } 434 | } 435 | 436 | const BlockedThinQRBaseType& m_qr; 437 | const DenseVectorType& m_other; 438 | bool m_transpose; 439 | }; 440 | 441 | template 442 | struct BlockedThinQRBaseMatrixQReturnType : public EigenBase > 443 | { 444 | typedef typename BlockedThinQRBaseType::Scalar Scalar; 445 | typedef Matrix DenseMatrix; 446 | enum { 447 | RowsAtCompileTime = Dynamic, 448 | ColsAtCompileTime = Dynamic 449 | }; 450 | explicit BlockedThinQRBaseMatrixQReturnType(const BlockedThinQRBaseType& qr) : m_qr(qr) {} 451 | /*BlockedThinQRBase_QProduct operator*(const MatrixBase& other) 452 | { 453 | return BlockedThinQRBase_QProduct(m_qr,other.derived(),false); 454 | }*/ 455 | template 456 | BlockedThinQRBase_QProduct operator*(const MatrixBase& other) 457 | { 458 | return BlockedThinQRBase_QProduct(m_qr, other.derived(), false); 459 | } 460 | template 461 | BlockedThinQRBase_QProduct> operator*(const SparseMatrix<_Scalar, _Options, _Index>& other) 462 | { 463 | return BlockedThinQRBase_QProduct>(m_qr, other, false); 464 | } 465 | BlockedThinQRBaseMatrixQTransposeReturnType adjoint() const 466 | { 467 | return BlockedThinQRBaseMatrixQTransposeReturnType(m_qr); 468 | } 469 | inline Index rows() const { return m_qr.rows(); } 470 | inline Index cols() const { return m_qr.rows(); } 471 | // To use for operations with the transpose of Q 472 | BlockedThinQRBaseMatrixQTransposeReturnType transpose() const 473 | { 474 | return BlockedThinQRBaseMatrixQTransposeReturnType(m_qr); 475 | } 476 | 477 | const BlockedThinQRBaseType& m_qr; 478 | }; 479 | 480 | template 481 | struct BlockedThinQRBaseMatrixQTransposeReturnType 482 | { 483 | explicit BlockedThinQRBaseMatrixQTransposeReturnType(const BlockedThinQRBaseType& qr) : m_qr(qr) {} 484 | template 485 | BlockedThinQRBase_QProduct operator*(const MatrixBase& other) 486 | { 487 | return BlockedThinQRBase_QProduct(m_qr, other.derived(), true); 488 | } 489 | template 490 | BlockedThinQRBase_QProduct> operator*(const SparseMatrix<_Scalar, _Options, _Index>& other) 491 | { 492 | return BlockedThinQRBase_QProduct>(m_qr, other, true); 493 | } 494 | const BlockedThinQRBaseType& m_qr; 495 | }; 496 | } // End namespace QRKit 497 | 498 | namespace Eigen { 499 | namespace internal { 500 | 501 | template 502 | struct evaluator_traits > 503 | { 504 | typedef typename BlockedThinQRBaseType::MatrixType MatrixType; 505 | typedef typename storage_kind_to_evaluator_kind::Kind Kind; 506 | typedef SparseShape Shape; 507 | }; 508 | 509 | template< typename DstXprType, typename BlockedThinQRBaseType> 510 | struct Assignment, assign_op, Sparse2Sparse> 511 | { 512 | typedef QRKit::BlockedThinQRBaseMatrixQReturnType SrcXprType; 513 | typedef typename DstXprType::Scalar Scalar; 514 | typedef typename DstXprType::StorageIndex StorageIndex; 515 | static void run(DstXprType &dst, const SrcXprType &src, const internal::assign_op &/*func*/) 516 | { 517 | typename DstXprType::PlainObject idMat(src.m_qr.rows(), src.m_qr.rows()); 518 | idMat.setIdentity(); 519 | dst = QRKit::BlockedThinQRBase_QProduct(src.m_qr, idMat, false); 520 | } 521 | }; 522 | 523 | template< typename DstXprType, typename BlockedThinQRBaseType> 524 | struct Assignment, assign_op, Sparse2Dense> 525 | { 526 | typedef QRKit::BlockedThinQRBaseMatrixQReturnType SrcXprType; 527 | typedef typename DstXprType::Scalar Scalar; 528 | typedef typename DstXprType::StorageIndex StorageIndex; 529 | static void run(DstXprType &dst, const SrcXprType &src, const internal::assign_op &/*func*/) 530 | { 531 | dst = src.m_qr.matrixQ() * DstXprType::Identity(src.m_qr.rows(), src.m_qr.rows()); 532 | } 533 | }; 534 | 535 | } // end namespace internal 536 | 537 | } // end namespace Eigen 538 | 539 | #endif // QRKIT_BLOCKED_THIN_QR_BASE_H 540 | -------------------------------------------------------------------------------- /src/QRKit/BlockedThinSparseQR.h: -------------------------------------------------------------------------------- 1 | // This file is part of Eigen, a lightweight C++ template library 2 | // for linear algebra. 3 | // 4 | // Copyright (C) 2017 Jan Svoboda 5 | // Copyright (C) 2016 Andrew Fitzgibbon 6 | // 7 | // This Source Code Form is subject to the terms of the Mozilla 8 | // Public License v. 2.0. If a copy of the MPL was not distributed 9 | // with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 10 | 11 | #ifndef QRKIT_BLOCKED_THIN_SPARSE_QR_H 12 | #define QRKIT_BLOCKED_THIN_SPARSE_QR_H 13 | 14 | #include 15 | #include 16 | #include 17 | #include "BlockedThinQRBase.h" 18 | 19 | namespace QRKit { 20 | 21 | template class BlockedThinSparseQR; 22 | } 23 | 24 | namespace Eigen { 25 | namespace internal { 26 | // BlockedThinSparseQR_traits 27 | template struct BlockedThinSparseQR_traits { }; 28 | template struct BlockedThinSparseQR_traits> { 29 | typedef Matrix Vector; 30 | }; 31 | template struct BlockedThinSparseQR_traits> { 32 | typedef SparseVector Vector; 33 | }; 34 | } // End namespace internal 35 | } // End namespace Eigen 36 | 37 | namespace QRKit { 38 | /** 39 | * \ingroup BlockedThinSparseQR_Module 40 | * \class BlockedThinSparseQR 41 | * \brief Sparse Householder QR Factorization for banded matrices 42 | * This implementation is not rank revealing and uses Eigen::HouseholderQR for solving the dense blocks. 43 | * 44 | * Q is the orthogonal matrix represented as products of Householder reflectors. 45 | * Use matrixQ() to get an expression and matrixQ().transpose() to get the transpose. 46 | * You can then apply it to a vector. 47 | * 48 | * R is the sparse triangular or trapezoidal matrix. The later occurs when A is rank-deficient. 49 | * matrixR().topLeftCorner(rank(), rank()) always returns a triangular factor of full rank. 50 | * 51 | * \tparam _MatrixType The type of the sparse matrix A, must be a column-major SparseMatrix<> 52 | * 53 | * \implsparsesolverconcept 54 | * 55 | * \warning The input sparse matrix A must be in compressed mode (see SparseMatrix::makeCompressed()). 56 | * 57 | */ 58 | template 59 | class BlockedThinSparseQR : public BlockedThinQRBase<_MatrixType, Eigen::ColPivHouseholderQR>, 60 | Eigen::SparseMatrix, _SuggestedBlockCols> 61 | { 62 | protected: 63 | typedef BlockedThinQRBase<_MatrixType, Eigen::ColPivHouseholderQR>, 64 | Eigen::SparseMatrix, _SuggestedBlockCols> Base; 65 | 66 | public: 67 | typedef _MatrixType MatrixType; 68 | typedef typename Base::Scalar Scalar; 69 | typedef typename Base::RealScalar RealScalar; 70 | typedef typename Base::StorageIndex StorageIndex; 71 | typedef typename Base::IndexVector IndexVector; 72 | typedef typename Base::DenseVectorType DenseVectorType; 73 | typedef typename Base::DenseMatrixType DenseMatrixType; 74 | 75 | typedef typename Base::MatrixQType MatrixQType; 76 | typedef typename Base::MatrixRType MatrixRType; 77 | typedef typename Base::PermutationType PermutationType; 78 | 79 | typedef typename Base::DenseBlockQR DenseBlockQR; 80 | 81 | typedef typename Base::BlockBandedMatrixInfo BlockBandedMatrixInfo; 82 | 83 | typedef Eigen::SparseMatrix RowMajorMatrixType; 84 | 85 | enum { 86 | ColsAtCompileTime = MatrixType::ColsAtCompileTime, 87 | MaxColsAtCompileTime = MatrixType::MaxColsAtCompileTime 88 | }; 89 | 90 | public: 91 | BlockedThinSparseQR() : Base() { 92 | } 93 | 94 | explicit BlockedThinSparseQR(const MatrixType& mat) : Base(mat) { 95 | } 96 | 97 | /** Computes the QR factorization of the sparse matrix \a mat. 98 | * 99 | * \warning The matrix \a mat must be in compressed mode (see SparseMatrix::makeCompressed()). 100 | * 101 | * If input pattern analysis has been successfully performed before, it won't be run again by default. 102 | * forcePatternAnalysis - if true, forces reruning pattern analysis of the input matrix 103 | * \sa analyzePattern(), factorize() 104 | */ 105 | virtual void compute(const MatrixType& mat) 106 | { 107 | // Reset variables in case this method is called multiple times 108 | this->m_isInitialized = false; 109 | this->m_factorizationIsok = false; 110 | this->m_blocksYT.clear(); 111 | this->m_nnzColPermIdxs.clear(); 112 | this->m_zeroColPermIdxs.clear(); 113 | 114 | // Analyze input matrix pattern and perform row and column permutations 115 | // Stores input matrix to m_pmat 116 | analyzePattern(mat); 117 | 118 | // Create dense version of the already permuted input matrix 119 | // It is much faster to do the permutations on the sparse version 120 | this->m_pmatDense = this->m_pmat.toDense(); 121 | 122 | // Initialize householder permutation matrix 123 | this->m_houseColPerm.setIdentity(this->m_pmatDense.cols()); 124 | 125 | // Reset nonzero pivots count 126 | this->m_nonzeroPivots = 0; 127 | 128 | // Prepare m_R to be filled in 129 | this->m_R.resize(this->m_pmatDense.rows(), this->m_pmatDense.cols()); 130 | this->m_R.setZero(); 131 | // Reserve number of elements needed in case m_R is full upper triangular 132 | this->m_R.reserve(this->m_pmatDense.cols() * this->m_pmatDense.cols() / 2.0); 133 | 134 | // And start factorizing block-by-block 135 | Index solvedCols = 0; 136 | Index cntr = 0; 137 | // As long as there are some unsolved columns 138 | Index newPivots = 0; 139 | while (solvedCols < this->m_pmatDense.cols()) { 140 | // Get next block info 141 | this->updateBlockInfo(solvedCols, this->m_pmat, newPivots, _SuggestedBlockCols); 142 | 143 | // Factorize current block 144 | newPivots = this->m_nonzeroPivots; 145 | factorize(this->m_pmatDense); 146 | newPivots = this->m_nonzeroPivots - newPivots; 147 | solvedCols += this->denseBlockInfo.numCols; 148 | } 149 | 150 | // Set computed Householder column permutation 151 | for (int c = 0; c < this->m_nnzColPermIdxs.size(); c++) { 152 | this->m_houseColPerm.indices()(c) = this->m_nnzColPermIdxs[c]; 153 | } 154 | for (int c = 0; c < this->m_zeroColPermIdxs.size(); c++) { 155 | this->m_houseColPerm.indices()(this->m_nnzColPermIdxs.size() + c) = this->m_zeroColPermIdxs[c]; 156 | } 157 | 158 | // Combine the two column permutation matrices together 159 | this->m_outputPerm_c = this->m_outputPerm_c * this->m_houseColPerm; 160 | 161 | // Don't forget to finalize m_R 162 | this->m_R.finalize(); 163 | 164 | this->m_isInitialized = true; 165 | this->m_info = Success; 166 | } 167 | 168 | virtual void analyzePattern(const MatrixType& mat, bool rowPerm = true, bool colPerm = true) { 169 | /******************************************************************/ 170 | // Create column permutation (according to the number of nonzeros in columns) 171 | if (colPerm) { 172 | SparseQROrdering::ColumnDensity colDenOrdering; 173 | colDenOrdering(mat, this->m_outputPerm_c); 174 | 175 | this->m_pmat = mat * this->m_outputPerm_c; 176 | } 177 | else { 178 | this->m_outputPerm_c.setIdentity(mat.cols()); 179 | 180 | // Don't waste time calling matrix multiplication if the permutation is identity 181 | this->m_pmat = mat; 182 | } 183 | 184 | /******************************************************************/ 185 | // Compute and store band information for each row in the matrix 186 | if (rowPerm) { 187 | RowMajorMatrixType rmMat(this->m_pmat); 188 | SparseQROrdering::AsBandedAsPossible abapOrdering; 189 | abapOrdering(rmMat, this->m_rowPerm); 190 | 191 | this->m_pmat = this->m_rowPerm * this->m_pmat; 192 | } 193 | else { 194 | this->m_rowPerm.setIdentity(this->m_pmat.rows()); 195 | 196 | // Don't waste time calling matrix multiplication if the permutation is identity 197 | } 198 | /******************************************************************/ 199 | 200 | this->m_analysisIsok = true; 201 | } 202 | 203 | virtual void updateBlockInfo(const Index solvedCols, const MatrixType& mat, const Index newPivots, const Index blockCols = -1) { 204 | Index newCols = (blockCols > 0) ? blockCols : _SuggestedBlockCols; 205 | Index colIdx = solvedCols + newCols; 206 | Index numRows = 0; 207 | if (colIdx >= this->m_pmat.cols()) { 208 | colIdx = this->m_pmat.cols() - 1; 209 | newCols = this->m_pmat.cols() - solvedCols; 210 | numRows = this->m_pmat.rows() - this->m_nonzeroPivots; 211 | } 212 | else { 213 | typename MatrixType::StorageIndex biggestEndIdx = 0; 214 | for (int c = 0; c < newCols; c++) { 215 | typename MatrixType::InnerIterator colIt(this->m_pmat, solvedCols + c); 216 | typename MatrixType::StorageIndex endIdx = 0; 217 | if (colIt) { 218 | endIdx = colIt.index(); 219 | } 220 | while (++colIt) { endIdx = colIt.index(); } 221 | 222 | if (endIdx > biggestEndIdx) { 223 | biggestEndIdx = endIdx; 224 | } 225 | } 226 | 227 | numRows = biggestEndIdx - this->m_nonzeroPivots + 1; 228 | if (numRows < (this->denseBlockInfo.numRows - newPivots)) { 229 | // In the next step we need to process at least all the rows we did in the last one 230 | // Even if the next block would be "shorter" 231 | numRows = this->denseBlockInfo.numRows - newPivots; 232 | } 233 | } 234 | 235 | this->denseBlockInfo = typename BlockBandedMatrixInfo::MatrixBlockInfo(this->m_nonzeroPivots, solvedCols, numRows, newCols); 236 | } 237 | 238 | virtual void factorize(DenseMatrixType& mat) { 239 | // Dense QR solver used for each dense block 240 | //Eigen::HouseholderQR houseqr; 241 | DenseBlockQR houseqr; 242 | 243 | // Prepare the first block 244 | DenseMatrixType Ji = m_pmatDense.block(this->denseBlockInfo.idxRow, this->denseBlockInfo.idxCol, this->denseBlockInfo.numRows, this->denseBlockInfo.numCols); 245 | 246 | /*********** Process the block ***********/ 247 | // 1) Factorize the block 248 | houseqr.compute(Ji); 249 | 250 | // Update column permutation according to ColPivHouseholderQR 251 | for (Index c = 0; c < houseqr.nonzeroPivots(); c++) { 252 | this->m_nnzColPermIdxs.push_back(this->denseBlockInfo.idxCol + houseqr.colsPermutation().indices()(c)); 253 | } 254 | for (Index c = houseqr.nonzeroPivots(); c < this->denseBlockInfo.numCols; c++) { 255 | this->m_zeroColPermIdxs.push_back(this->denseBlockInfo.idxCol + houseqr.colsPermutation().indices()(c)); 256 | } 257 | 258 | // 2) Create matrices Y and T 259 | DenseMatrixType Y, T; 260 | this->computeBlockedRepresentation(houseqr, Y, T); 261 | 262 | // Save current Y and T. The block YTY contains a main diagonal and subdiagonal part separated by (numZeros) zero rows. 263 | this->m_blocksYT.insert(typename SparseBlockYTY::Element(this->denseBlockInfo.idxRow, this->denseBlockInfo.idxCol, BlockYTY(Y, T, this->denseBlockInfo.idxRow, this->denseBlockInfo.idxCol, 0))); 264 | 265 | // Update the trailing columns of the matrix block 266 | this->updateMat(this->denseBlockInfo.idxCol, m_pmatDense.cols(), m_pmatDense, this->m_blocksYT.size() - 1); 267 | 268 | // Add solved columns to R 269 | // m_nonzeroPivots is telling us where is the current diagonal position 270 | // Don't forget to add the upper overlap (anything above the current diagonal element is already processed, but is part of R 271 | for (typename MatrixType::StorageIndex bc = 0; bc < this->denseBlockInfo.numCols; bc++) { 272 | this->m_R.startVec(this->m_nonzeroPivots + bc); 273 | for (typename MatrixType::StorageIndex br = 0; br < this->m_nonzeroPivots; br++) { 274 | this->m_R.insertBack(br, this->m_nonzeroPivots + bc) = this->m_pmatDense(br, this->denseBlockInfo.idxCol + houseqr.colsPermutation().indices()(bc)); 275 | } 276 | for (typename MatrixType::StorageIndex br = 0; br <= bc; br++) { 277 | this->m_R.insertBack(this->m_nonzeroPivots + br, this->m_nonzeroPivots + bc) = houseqr.matrixQR()(br, bc); 278 | } 279 | } 280 | 281 | // Add nonzero pivots from this block 282 | this->m_nonzeroPivots += houseqr.nonzeroPivots(); 283 | } 284 | 285 | protected: 286 | MatrixType m_pmat; // Sparse version of input matrix - used for ordering and search purposes 287 | DenseMatrixType m_pmatDense; // Dense version of the input matrix - used for factorization (much faster than using sparse) 288 | 289 | PermutationType m_houseColPerm; 290 | std::vector m_nnzColPermIdxs; 291 | std::vector m_zeroColPermIdxs; 292 | }; 293 | 294 | } // end namespace QRKit 295 | 296 | #endif 297 | -------------------------------------------------------------------------------- /src/QRKit/QRKit: -------------------------------------------------------------------------------- 1 | // This file is part of Eigen, a lightweight C++ template library 2 | // for linear algebra. 3 | // 4 | // Copyright (C) 2017 Jan Svoboda 5 | // Copyright (C) 2017 Andrew Fitzgibbon 6 | // Copyright (C) 2016 Sergio Garrido Jurado 7 | // 8 | // This Source Code Form is subject to the terms of the Mozilla 9 | // Public License v. 2.0. If a copy of the MPL was not distributed 10 | // with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 11 | 12 | #ifndef QRKIT_SPARSE_QR_EXTRA_MODULE_H 13 | #define QRKIT_SPARSE_QR_EXTRA_MODULE_H 14 | 15 | #include "Eigen/Sparse" 16 | 17 | // #include "../../Eigen/src/Core/util/DisableStupidWarnings.h" 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | /** 28 | * \defgroup SparseQR_Module SparseQR module 29 | * 30 | * This module extends the Eigen default SparseQR with special QR 31 | * solvers for block diagonal, row banded matrices and block angular. 32 | * 33 | * \code 34 | * #include 35 | * \endcode 36 | */ 37 | 38 | #include "QRKit/SparseQRUtils.h" 39 | #include "QRKit/SparseQROrdering.h" 40 | #include "QRKit/BlockYTY.h" 41 | #include "QRKit/SparseBlockCOO.h" 42 | #include "QRKit/SparseBlockDiagonal.h" 43 | #include "QRKit/BlockMatrix1x2.h" 44 | #include "QRKit/SparseBlockYTY.h" 45 | #include "QRKit/BlockDiagonalSparseQR.h" 46 | #include "QRKit/BandedBlockedSparseQR.h" 47 | #include "QRKit/BlockedThinSparseQR.h" 48 | #include "QRKit/BlockedThinDenseQR.h" 49 | #include "QRKit/BlockAngularSparseQR.h" 50 | 51 | // #include "../../Eigen/src/Core/util/ReenableStupidWarnings.h" 52 | 53 | #endif // EIGEN_SPARSE_QR_EXTRA_MODULE_H -------------------------------------------------------------------------------- /src/QRKit/SparseBlockCOO.h: -------------------------------------------------------------------------------- 1 | // This file is part of Eigen, a lightweight C++ template library 2 | // for linear algebra. 3 | // 4 | // Copyright (C) 2017 Jan Svoboda 5 | // Copyright (C) 2016 Andrew Fitzgibbon 6 | // 7 | // This Source Code Form is subject to the terms of the Mozilla 8 | // Public License v. 2.0. If a copy of the MPL was not distributed 9 | // with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 10 | 11 | #ifndef SPARSE_BLOCK_COO_H 12 | #define SPARSE_BLOCK_COO_H 13 | 14 | #include 15 | 16 | namespace QRKit { 17 | /* 18 | * Storage type for general sparse matrix with block structure. 19 | * Each element holds block position (row index, column index) and the values in the block stored in ValueType. 20 | * ValueType is a template type and can generally represent any datatype, both default and user defined. 21 | * 22 | */ 23 | template 24 | class SparseBlockCOO { 25 | public: 26 | /* 27 | * Building block of the SparseBlockCOO matrix. 28 | * Parameters 'row' and 'col' are the indices of the top left corner of the block. 29 | * For example: 30 | * 11111 31 | * 11111 32 | * 1113322 33 | * 2222 34 | * is {Element(0, 0, MatrixXd::Ones(3, 5)), Element(2, 3, MatrixXd::Ones(2, 4))} 35 | * 36 | */ 37 | struct Element { 38 | IndexType row; 39 | IndexType col; 40 | 41 | ValueType value; 42 | 43 | Element() 44 | : row(0), col(0) { 45 | } 46 | 47 | Element(const IndexType row, const IndexType col, const ValueType &val) 48 | : row(row), col(col), value(val) { 49 | } 50 | }; 51 | typedef std::vector ElementsVec; 52 | 53 | SparseBlockCOO() 54 | : nRows(0), nCols(0) { 55 | } 56 | 57 | SparseBlockCOO(const IndexType &rows, const IndexType &cols) 58 | : nRows(rows), nCols(cols) { 59 | } 60 | 61 | void insert(const Element &elem) { 62 | this->elems.push_back(elem); 63 | } 64 | 65 | IndexType size() const { 66 | return this->elems.size(); 67 | } 68 | 69 | void clear() { 70 | this->elems.clear(); 71 | } 72 | 73 | Element& operator[](IndexType i) { 74 | return this->elems[i]; 75 | } 76 | const Element& operator[](IndexType i) const { 77 | return this->elems[i]; 78 | } 79 | 80 | IndexType rows() const { 81 | return this->nRows; 82 | } 83 | IndexType cols() const { 84 | return this->nCols; 85 | } 86 | 87 | protected: 88 | ElementsVec elems; 89 | 90 | IndexType nRows; 91 | IndexType nCols; 92 | }; 93 | } 94 | 95 | #endif 96 | 97 | -------------------------------------------------------------------------------- /src/QRKit/SparseBlockDiagonal.h: -------------------------------------------------------------------------------- 1 | // This file is part of Eigen, a lightweight C++ template library 2 | // for linear algebra. 3 | // 4 | // Copyright (C) 2017 Jan Svoboda 5 | // Copyright (C) 2016 Andrew Fitzgibbon 6 | // 7 | // This Source Code Form is subject to the terms of the Mozilla 8 | // Public License v. 2.0. If a copy of the MPL was not distributed 9 | // with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 10 | 11 | #ifndef SPARSE_BLOCK_DIAGONAL_H 12 | #define SPARSE_BLOCK_DIAGONAL_H 13 | 14 | #include 15 | #include "SparseBlockCOO.h" 16 | #include "SparseQRUtils.h" 17 | 18 | namespace QRKit { 19 | 20 | /* 21 | * Container for memory efficient storing block diagonal matrices. 22 | * Each block on the diagonal can have different size. 23 | * Block angular matrix is a matrix of the following form: 24 | * 25 | * XXX 26 | * XXX 27 | * XXX 28 | * XXX 29 | * XXX 30 | * XXX 31 | * XXX 32 | * XXX 33 | * XXX 34 | * XXX 35 | * XXX 36 | * XXX 37 | * XXX 38 | * XXX 39 | * 40 | * The matrix is typically very sparse. 41 | * This container provides also method for constructing block diagonal matrices from Eigen::SparseMatrix. 42 | */ 43 | template 44 | class SparseBlockDiagonal { 45 | public: 46 | typedef std::vector BlockVec; 47 | typedef typename BlockMatrixType::RealScalar RealScalar; 48 | typedef typename BlockMatrixType::Scalar Scalar; 49 | typedef _StorageIndex StorageIndex; 50 | typedef typename BlockMatrixType::Index Index; 51 | typedef PermutationMatrix PermutationType; 52 | 53 | /* 54 | * Stores information about a dense block in a block sparse matrix. 55 | * Holds the position of the block (row index, column index) and its size (number of rows, number of columns). 56 | */ 57 | typedef typename SparseQRUtils::BlockBandedMatrixInfo BlockDiagonalMatrixInfo; 58 | 59 | SparseBlockDiagonal() 60 | : nRows(0), nCols(0) { 61 | } 62 | 63 | SparseBlockDiagonal(const StorageIndex &rows, const StorageIndex &cols) 64 | : nRows(rows), nCols(cols) { 65 | } 66 | 67 | /* 68 | * This method is expecting that the given input matrix is already in block diagonal format, 69 | * having equally-sized diagonal blocks of size (blockRows, blockcols) 70 | */ 71 | template 72 | void fromBlockDiagonalPattern(const MatrixType& mat, const StorageIndex blockRows, const StorageIndex blockCols) { 73 | this->clear(); 74 | this->nRows = mat.rows(); 75 | this->nCols = mat.cols(); 76 | 77 | // Get matrix block info 78 | BlockDiagonalMatrixInfo blockInfo; 79 | blockInfo.fromBlockDiagonalPattern(mat.rows(), mat.cols(), blockRows, blockCols); 80 | 81 | // Feed matrix into the memory efficient storage 82 | int numBlocks = blockInfo.blockOrder.size(); 83 | typename BlockDiagonalMatrixInfo::MatrixBlockInfo bi; 84 | for (int i = 0; i < numBlocks; i++) { 85 | bi = blockInfo.blockMap.at(blockInfo.blockOrder.at(i)); 86 | 87 | this->insertBack(BlockMatrixType(mat.block(bi.idxRow, bi.idxCol, bi.numRows, bi.numCols))); 88 | } 89 | } 90 | 91 | /* 92 | * If row permutation is needed to obtain block diagonal matrix out of the input sparse matrix, 93 | * this permutation is returned as reference via an optional parameter for further use. 94 | */ 95 | template 96 | void fromSparseMatrix(const MatrixType& mat, PermutationType &rowPerm = PermutationType()) { 97 | typedef SparseMatrix RowMajorMatrixType; 98 | 99 | this->clear(); 100 | this->nRows = mat.rows(); 101 | this->nCols = mat.cols(); 102 | 103 | // Get matrix block info 104 | BlockDiagonalMatrixInfo blockInfo; 105 | 106 | /* We need to do generic analysis of the input matrix 107 | Looking for as-banded-as-possible structure in the matrix 108 | Expecting to form something block diagonal in this case 109 | */ 110 | SparseQROrdering::AsBandedAsPossible abapOrdering; 111 | RowMajorMatrixType rmMat(mat); 112 | abapOrdering(rmMat, rowPerm); 113 | 114 | // Permute if permutation found 115 | if (abapOrdering.hasPermutation) { 116 | rmMat = rowPerm * rmMat; 117 | } 118 | 119 | // Compute matrix block structure 120 | blockInfo(rmMat); 121 | 122 | // Feed matrix into the memory efficient storage 123 | int numBlocks = blockInfo.blockOrder.size(); 124 | typename BlockDiagonalMatrixInfo::MatrixBlockInfo bi; 125 | for (int i = 0; i < numBlocks; i++) { 126 | bi = blockInfo.blockMap.at(blockInfo.blockOrder.at(i)); 127 | 128 | this->insertBack(BlockMatrixType(mat.block(bi.idxRow, bi.idxCol, bi.numRows, bi.numCols))); 129 | } 130 | } 131 | 132 | void insertBack(const BlockMatrixType &elem) { 133 | this->blocks.push_back(elem); 134 | } 135 | 136 | StorageIndex size() const { 137 | return this->blocks.size(); 138 | } 139 | 140 | void clear() { 141 | this->blocks.clear(); 142 | } 143 | 144 | BlockMatrixType& operator[](StorageIndex i) { 145 | return this->blocks[i]; 146 | } 147 | const BlockMatrixType& operator[](StorageIndex i) const { 148 | return this->blocks[i]; 149 | } 150 | 151 | StorageIndex rows() const { 152 | return this->nRows; 153 | } 154 | StorageIndex cols() const { 155 | return this->nCols; 156 | } 157 | 158 | protected: 159 | BlockVec blocks; 160 | 161 | StorageIndex nRows; 162 | StorageIndex nCols; 163 | }; 164 | } 165 | 166 | #endif 167 | 168 | -------------------------------------------------------------------------------- /src/QRKit/SparseBlockYTY.h: -------------------------------------------------------------------------------- 1 | // This file is part of Eigen, a lightweight C++ template library 2 | // for linear algebra. 3 | // 4 | // Copyright (C) 2017 Jan Svoboda 5 | // Copyright (C) 2016 Andrew Fitzgibbon 6 | // 7 | // This Source Code Form is subject to the terms of the Mozilla 8 | // Public License v. 2.0. If a copy of the MPL was not distributed 9 | // with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 10 | 11 | #ifndef SPARSE_BLOCK_YTY_H 12 | #define SPARSE_BLOCK_YTY_H 13 | 14 | #include 15 | #include "SparseBlockCOO.h" 16 | #include "BlockYTY.h" 17 | 18 | namespace QRKit { 19 | template class SparseBlocksYTY; 20 | template struct SparseBlockYTYProductReturnType; 21 | template struct SparseBlockYTYProductTransposeReturnType; 22 | template struct SparseBlockYTY_VecProduct; 23 | } 24 | 25 | namespace Eigen { 26 | namespace internal { 27 | 28 | // traits 29 | template struct traits > 30 | { 31 | typedef typename SparseBlockYTYType::MatrixType ReturnType; 32 | typedef typename ReturnType::StorageIndex StorageIndex; 33 | typedef typename ReturnType::StorageKind StorageKind; 34 | enum { 35 | RowsAtCompileTime = Dynamic, 36 | ColsAtCompileTime = Dynamic 37 | }; 38 | }; 39 | 40 | template struct traits > 41 | { 42 | typedef typename SparseBlockYTYType::MatrixType ReturnType; 43 | }; 44 | 45 | template struct traits > 46 | { 47 | typedef typename Derived::PlainObject ReturnType; 48 | }; 49 | 50 | // BlockYTY_traits 51 | template struct SparseBlockYTY_traits { }; 52 | 53 | } // End namespace internal 54 | } // End namespace Eigen 55 | 56 | namespace QRKit { 57 | /* 58 | * SparseBlockCOO specialization for sparse matrix storing YT blocks. 59 | * Each element holds block position (row index, column index) and the values in the block stored in ValueType. 60 | * ValueType is a template type and can generally represent any datatype, both default and user defined. 61 | * 62 | * This specialization is important for good performance of the YTY product evaluation. 63 | * Each call to yty() expression of BlockYTY class includes some additional time-consuming processing. 64 | * yty() is therefore not suitable for being used inside for loops. For such reasons, this specialization 65 | * of SparseBlockCOO for storing BlockYTY elements provides an expression sequenceYTY(), which internally 66 | * handles series of YTY multiplications in much more efficient way. 67 | * 68 | */ 69 | template 70 | class SparseBlockYTY : public SparseBlockCOO, IndexType> { 71 | public: 72 | typedef typename BlockYTY::MatrixType MatrixType; 73 | 74 | SparseBlockYTY() 75 | : SparseBlockCOO, IndexType>(0, 0) { 76 | } 77 | 78 | SparseBlockYTY(const IndexType& rows, const IndexType& cols) 79 | : SparseBlockCOO, IndexType>(rows, cols) { 80 | } 81 | 82 | SparseBlockYTYProductReturnType sequenceYTY() const { 83 | return SparseBlockYTYProductReturnType(*this); 84 | } 85 | 86 | protected: 87 | template friend struct SparseBlockYTY_VecProduct; 88 | }; 89 | 90 | /************************************ Expression templates for the evaluation of single YTY product ***************************************/ 91 | template 92 | struct SparseBlockYTY_VecProduct : Eigen::ReturnByValue > 93 | { 94 | // Get the references 95 | SparseBlockYTY_VecProduct(const SparseBlockYTYType& blocksYT, const Derived& other, bool transpose) : 96 | m_blocksYT(blocksYT), m_other(other), m_transpose(transpose) {} 97 | inline Index rows() const { return m_other.rows(); } 98 | inline Index cols() const { return m_other.cols(); } 99 | 100 | // Assign to a vector 101 | template 102 | void evalTo(DesType& res) const 103 | { 104 | res = m_other; 105 | 106 | /* 107 | * While expressing product of multiple YTY blocks using for loop, it is much more efficient to write the YTY expressions 108 | * explicitly rather than using yty() expression. 109 | */ 110 | Derived segmentVec; 111 | if (m_transpose) { 112 | for (Index k = 0; k < m_blocksYT.size(); k++) { 113 | SparseQRUtils::SegmentDescriptors segDescs = { 114 | { m_blocksYT[k].row, m_blocksYT[k].value.cols() }, 115 | { m_blocksYT[k].row + m_blocksYT[k].value.cols() + m_blocksYT[k].value.numZeros(), m_blocksYT[k].value.rows() - m_blocksYT[k].value.cols() } 116 | }; 117 | SparseQRUtils::getVectorSegments(segmentVec, res, segDescs, m_blocksYT[k].value.rows()); 118 | 119 | // Non-aliasing expr -> noalias() 120 | segmentVec.noalias() += (m_blocksYT[k].value.Y() * (m_blocksYT[k].value.T().transpose() * (m_blocksYT[k].value.Y().transpose() * segmentVec))); 121 | 122 | SparseQRUtils::setVectorSegments(res, segmentVec, segDescs); 123 | } 124 | } 125 | else { 126 | for (Index k = m_blocksYT.size() - 1; k >= 0; k--) { 127 | SparseQRUtils::SegmentDescriptors segDescs = { 128 | { m_blocksYT[k].row, m_blocksYT[k].value.cols() }, 129 | { m_blocksYT[k].row + m_blocksYT[k].value.cols() + m_blocksYT[k].value.numZeros(), m_blocksYT[k].value.rows() - m_blocksYT[k].value.cols() } 130 | }; 131 | SparseQRUtils::getVectorSegments(segmentVec, res, segDescs, m_blocksYT[k].value.rows()); 132 | 133 | // Non-aliasing expr -> noalias() 134 | segmentVec.noalias() += (m_blocksYT[k].value.Y() * (m_blocksYT[k].value.T() * (m_blocksYT[k].value.Y().transpose() * segmentVec))); 135 | 136 | SparseQRUtils::setVectorSegments(res, segmentVec, segDescs); 137 | } 138 | } 139 | } 140 | 141 | const SparseBlockYTYType& m_blocksYT; 142 | const Derived& m_other; 143 | bool m_transpose; 144 | }; 145 | 146 | template 147 | struct SparseBlockYTYProductReturnType : public EigenBase > 148 | { 149 | enum { 150 | RowsAtCompileTime = Dynamic, 151 | ColsAtCompileTime = Dynamic 152 | }; 153 | explicit SparseBlockYTYProductReturnType(const SparseBlockYTYType& blocksYT) : m_blocksYT(blocksYT) {} 154 | 155 | template 156 | SparseBlockYTY_VecProduct operator*(const MatrixBase& other) 157 | { 158 | return SparseBlockYTY_VecProduct(m_blocksYT, other.derived(), false); 159 | } 160 | template 161 | SparseBlockYTY_VecProduct> operator*(const SparseMatrix<_Scalar, _Options, _Index>& other) 162 | { 163 | return SparseBlockYTY_VecProduct>(m_blocksYT, other, false); 164 | } 165 | SparseBlockYTYProductTransposeReturnType adjoint() const 166 | { 167 | return SparseBlockYTYProductTransposeReturnType(m_blocksYT); 168 | } 169 | SparseBlockYTYProductTransposeReturnType transpose() const 170 | { 171 | return SparseBlockYTYProductTransposeReturnType(m_blocksYT); 172 | } 173 | 174 | const SparseBlockYTYType& m_blocksYT; 175 | }; 176 | 177 | template 178 | struct SparseBlockYTYProductTransposeReturnType 179 | { 180 | explicit SparseBlockYTYProductTransposeReturnType(const SparseBlockYTYType& blocksYT) : m_blocksYT(blocksYT) {} 181 | template 182 | SparseBlockYTY_VecProduct operator*(const MatrixBase& other) 183 | { 184 | return SparseBlockYTY_VecProduct(m_blocksYT, other.derived(), true); 185 | } 186 | template 187 | SparseBlockYTY_VecProduct> operator*(const SparseMatrix<_Scalar, _Options, _Index>& other) 188 | { 189 | return SparseBlockYTY_VecProduct>(m_blocksYT, other, true); 190 | } 191 | const SparseBlockYTYType& m_blocksYT; 192 | }; 193 | 194 | } // End namespace QRKit 195 | 196 | namespace Eigen { 197 | namespace internal { 198 | template 199 | struct evaluator_traits > 200 | { 201 | typedef typename SparseBlockYTYType::MatrixType MatrixType; 202 | typedef typename storage_kind_to_evaluator_kind::Kind Kind; 203 | typedef SparseShape Shape; 204 | }; 205 | } 206 | /************************************************************************************************************************************/ 207 | } // End namespace Eigen 208 | 209 | #endif 210 | 211 | -------------------------------------------------------------------------------- /src/QRKit/SparseQROrdering.h: -------------------------------------------------------------------------------- 1 | // This file is part of Eigen, a lightweight C++ template library 2 | // for linear algebra. 3 | // 4 | // Copyright (C) 2017 Jan Svoboda 5 | // Copyright (C) 2016 Andrew Fitzgibbon 6 | // 7 | // This Source Code Form is subject to the terms of the Mozilla 8 | // Public License v. 2.0. If a copy of the MPL was not distributed 9 | // with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 10 | 11 | #ifndef SPARSE_QR_ORDERING_H 12 | #define SPARSE_QR_ORDERING_H 13 | 14 | #include "SparseQRUtils.h" 15 | 16 | using namespace Eigen; 17 | 18 | namespace QRKit { 19 | namespace SparseQROrdering { 20 | 21 | template 22 | class ColumnDensity { 23 | public: 24 | typedef PermutationMatrix PermutationType; 25 | 26 | /* 27 | * Compute the permutation vector from a column-major sparse matrix. 28 | * Create column permutation (according to the number of nonzeros in columns. 29 | */ 30 | template 31 | void operator()(const MatrixType &mat, PermutationType &perm) { 32 | eigen_assert(!mat.IsRowMajor && "Input matrix has to be ColMajor!"); 33 | 34 | typedef SparseQRUtils::VectorCount MatrixColCount; 35 | 36 | std::vector colNnzs; 37 | // Record number of nonzeros in columns 38 | for (Index c = 0; c < mat.cols(); c++) { 39 | colNnzs.push_back(MatrixColCount(c, mat.col(c).nonZeros())); 40 | } 41 | // Sort the column according to the number of nonzeros in an ascending order 42 | std::stable_sort(colNnzs.begin(), colNnzs.end()); 43 | // Create permutation matrix out of the sorted vector 44 | Eigen::Matrix colpermIndices(colNnzs.size()); 45 | for (Index c = 0; c < colNnzs.size(); c++) { 46 | colpermIndices(colNnzs[c].origIdx) = c; 47 | } 48 | perm = PermutationType(colpermIndices); 49 | } 50 | }; 51 | 52 | template 53 | class AsBandedAsPossible { 54 | public: 55 | typedef PermutationMatrix PermutationType; 56 | 57 | bool hasPermutation; 58 | 59 | /* 60 | * Compute the permutation vector from a row-major sparse matrix. 61 | * Look for as banded as possible structure in the matrix. 62 | * Row band-widths are detected and rows are sorted by the band start index. 63 | * In addition, it records auxiliary information about the banded structure of the permuted matrix. 64 | */ 65 | template 66 | void operator()(const MatrixType &mat, PermutationType &perm) { 67 | eigen_assert(mat.IsRowMajor && "Input matrix has to be RowMajor!"); 68 | 69 | typedef SparseQRUtils::RowRange MatrixRowRange; 70 | 71 | // 1) Compute and store band information for each row in the matrix 72 | std::vector rowRanges; 73 | // Compute band information for each row 74 | for (typename MatrixType::StorageIndex j = 0; j < mat.rows(); j++) { 75 | typename MatrixType::InnerIterator rowIt(mat, j); 76 | typename MatrixType::StorageIndex startIdx = mat.cols(); 77 | if (rowIt) { // Necessary from the nature of the while loop below 78 | startIdx = rowIt.index(); 79 | } 80 | typename MatrixType::StorageIndex endIdx = startIdx; 81 | while (++rowIt) { endIdx = rowIt.index(); } // FixMe: Is there a better way? 82 | rowRanges.push_back(MatrixRowRange(j, startIdx, endIdx)); 83 | } 84 | 85 | // 2) Sort the rows to form as-banded-as-possible matrix 86 | // Set an indicator whether row sorting is needed 87 | this->hasPermutation = !std::is_sorted(rowRanges.begin(), rowRanges.end(), [](const MatrixRowRange &lhs, const MatrixRowRange &rhs) { 88 | return (lhs.start < rhs.start); 89 | }); 90 | // Perform the actual row sorting if needed 91 | if (this->hasPermutation) { 92 | std::stable_sort(rowRanges.begin(), rowRanges.end(), [](const MatrixRowRange &lhs, const MatrixRowRange &rhs) { 93 | return (lhs.start < rhs.start); 94 | /*if (lhs.start < rhs.start) { 95 | return true; 96 | } 97 | else if (lhs.start == rhs.start) { 98 | if (lhs.end < rhs.end) { 99 | return true; 100 | } 101 | else { 102 | return lhs.origIdx < rhs.origIdx; 103 | } 104 | } 105 | else { 106 | return false; 107 | }*/ 108 | }); 109 | } 110 | 111 | // And record the estimated block structure 112 | Eigen::Matrix permIndices(rowRanges.size()); 113 | typename MatrixType::StorageIndex rowIdx = 0; 114 | for (auto it = rowRanges.begin(); it != rowRanges.end(); ++it, rowIdx++) { 115 | permIndices(it->origIdx) = rowIdx; 116 | } 117 | // Create row permutation matrix that achieves the desired row reordering 118 | perm = PermutationType(permIndices); 119 | } 120 | }; 121 | 122 | } 123 | } 124 | 125 | #endif 126 | 127 | -------------------------------------------------------------------------------- /src/QRKit/SparseQRUtils.h: -------------------------------------------------------------------------------- 1 | // This file is part of Eigen, a lightweight C++ template library 2 | // for linear algebra. 3 | // 4 | // Copyright (C) 2017 Jan Svoboda 5 | // Copyright (C) 2016 Andrew Fitzgibbon 6 | // 7 | // This Source Code Form is subject to the terms of the Mozilla 8 | // Public License v. 2.0. If a copy of the MPL was not distributed 9 | // with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 10 | 11 | #ifndef SPARSE_QR_UTILS_H 12 | #define SPARSE_QR_UTILS_H 13 | 14 | #include 15 | 16 | namespace QRKit { 17 | namespace SparseQRUtils { 18 | 19 | /*********************** Sparse QR solver traits class **************************** 20 | * Necessary because not all the solvers have the rowsPermutation() method. 21 | ***********************************************************************************/ 22 | template struct HasRowsPermutation { 23 | static const bool value = false; 24 | }; 25 | 26 | template 27 | typename std::enable_if::value, PermType>::type rowsPermutation(const SolverType &slvr) { 28 | return slvr.rowsPermutation(); 29 | } 30 | template 31 | typename std::enable_if::value, PermType>::type rowsPermutation(const SolverType &slvr) { 32 | PermType perm; 33 | perm.setIdentity(slvr.rows()); 34 | return perm; 35 | } 36 | 37 | /* 38 | * Templated functions allowing to copy multiple segments from a vector type. 39 | * Starting positions and lengths of the segments are specificed as std::pair, as: 40 | * .first ... starting index of the segment 41 | * .second ... length of the segment 42 | * The definition of the segments is then passed as vector of pairs. 43 | */ 44 | typedef std::pair SegmentDesc; 45 | typedef std::vector SegmentDescriptors; 46 | // Create new dense vector from a segments of another dense vector 47 | template 48 | void getVectorSegments(VectorType &dst, const VectorType &src, const SegmentDescriptors &segments, int totalLength = -1) { 49 | // Total length (sum of lengths of the segments) can be provided as a template parameters 50 | if (totalLength > 0) { 51 | dst = VectorType(totalLength); 52 | } 53 | // If total length is not provided, compute it from the vector of segments automatically (presumably slower) 54 | else { 55 | int length = 0; 56 | for (auto seg = segments.begin(); seg != segments.end(); ++seg) { 57 | length += seg->second; 58 | } 59 | dst = VectorType(length); 60 | } 61 | 62 | // Copy the segments of the source vector into the destination vector 63 | if (NumSegments == 2) { 64 | dst.segment(0, segments[0].second) = src.segment(segments[0].first, segments[0].second); 65 | dst.segment(segments[0].second, segments[1].second) = src.segment(segments[1].first, segments[1].second); 66 | } 67 | else { 68 | int currPos = 0; 69 | for (auto seg = segments.begin(); seg != segments.end(); ++seg) { 70 | dst.segment(currPos, seg->second) = src.segment(seg->first, seg->second); 71 | currPos += seg->second; 72 | } 73 | } 74 | } 75 | // Set segments of a dense vector from another dense vector 76 | template 77 | void setVectorSegments(VectorType &dst, const VectorType &src, const SegmentDescriptors &segments) { 78 | if (NumSegments == 2) { 79 | dst.segment(segments[0].first, segments[0].second) = src.segment(0, segments[0].second); 80 | dst.segment(segments[1].first, segments[1].second) = src.segment(segments[0].second, segments[1].second); 81 | } 82 | else { 83 | int currPos = 0; 84 | for (auto seg = segments.begin(); seg != segments.end(); ++seg) { 85 | dst.segment(seg->first, seg->second) = src.segment(currPos, seg->second); 86 | currPos += seg->second; 87 | } 88 | } 89 | } 90 | 91 | /* 92 | * Stores information about a dense block in a block sparse matrix. 93 | * Holds the position of the block (row index, column index) and its size (number of rows, number of columns). 94 | */ 95 | template 96 | struct BlockInfo { 97 | typedef std::map> Map; 98 | typedef std::vector MapOrder; 99 | 100 | IndexType idxRow; 101 | IndexType idxCol; 102 | IndexType numRows; 103 | IndexType numCols; 104 | 105 | BlockInfo() 106 | : idxRow(0), idxCol(0), numRows(0), numCols(0) { 107 | } 108 | 109 | BlockInfo(const IndexType &rowIdx, const IndexType &colIdx, const IndexType &nr, const IndexType &nc) 110 | : idxRow(rowIdx), idxCol(colIdx), numRows(nr), numCols(nc) { 111 | } 112 | 113 | BlockInfo(const IndexType &diagIdx, const IndexType &nr, const IndexType &nc) 114 | : idxRow(diagIdx), idxCol(diagIdx), numRows(nr), numCols(nc) { 115 | } 116 | }; 117 | /* 118 | * Overloaded << operator for the BlockInfo struct to easily output block position and size. 119 | */ 120 | template 121 | std::ostream& operator<<(std::ostream& os, const BlockInfo &bi) { 122 | std::cout << "[" << bi.idxRow << ", " << bi.idxCol << "] = " << bi.numRows << ", " << bi.numCols; 123 | return os; 124 | } 125 | 126 | /* 127 | * Helper structure holding band information for a single row. 128 | * Stores original row index (before any row reordering was performed), 129 | * index of the first nonzero (start) and last nonzero(end) in the band and the band length (length). 130 | */ 131 | template 132 | struct RowRange { 133 | IndexType origIdx; 134 | IndexType start; 135 | IndexType end; 136 | IndexType length; 137 | 138 | RowRange() : start(0), end(0), length(0) { 139 | } 140 | 141 | RowRange(const IndexType &origIdx, const IndexType &start, const IndexType &end) 142 | : origIdx(origIdx), start(start), end(end) { 143 | this->length = this->end - this->start + 1; 144 | } 145 | }; 146 | 147 | /* 148 | * Helper structure holding nonzero count for a vector in a matrix. 149 | * Stores original row/column index (before any row reordering was performed), 150 | * and nthe number of nonzero elements (numNnz). 151 | */ 152 | template 153 | struct VectorCount { 154 | IndexType origIdx; 155 | IndexType numNnz; 156 | 157 | VectorCount() : origIdx(0), numNnz(0) { 158 | } 159 | 160 | VectorCount(const IndexType &origIdx, const IndexType &numNnz) 161 | : origIdx(origIdx), numNnz(numNnz) { 162 | } 163 | 164 | bool operator<(const VectorCount &right) const { 165 | return this->numNnz < right.numNnz; 166 | } 167 | }; 168 | 169 | /* 170 | * Stores information about block bands of the input row-major matrix. 171 | * SuggestedBlockCols is telling the algorithm how many columns per block is ideally desired. 172 | * The output block structure does not have to produce block with SuggestedBlockCols column, 173 | * but aims to get at least close to it. SuggestedBlockCols is a lower bound on the block width. 174 | * The algorithm seeks only valid blocks: nrows >= ncols. If an invalid block is constructed, the encapsulating 175 | * factorization algorithm will not work properly. 176 | * If valid block (nrows >= ncols) cannot be constructed with SuggestedBlockCols column, it is beign extended 177 | * by the smallest number of columns that assure (nrows >= ncols). 178 | */ 179 | template 180 | struct BlockBandedMatrixInfo { 181 | typedef SparseQRUtils::BlockInfo MatrixBlockInfo; 182 | typename MatrixBlockInfo::Map blockMap; // Sparse matrix block information 183 | typename MatrixBlockInfo::MapOrder blockOrder; // Sparse matrix block order 184 | IndexType nonZeroQEstimate; // Estimate of number of nonzero elements in Q matrix formed by QR decomposition of the analyzed matrix 185 | 186 | template 187 | void operator()(const MatrixType &mat) { 188 | eigen_assert(mat.IsRowMajor && "Input matrix has to be RowMajor!"); 189 | 190 | typedef SparseQRUtils::RowRange MatrixRowRange; 191 | typedef std::map BlockBandSize; 192 | // 1) Compute and store band information for each start index that has nonzero in column i 193 | BlockBandSize bandWidths, bandHeights; 194 | std::vector rowRanges; 195 | for (typename MatrixType::StorageIndex j = 0; j < mat.rows(); j++) { 196 | typename MatrixType::InnerIterator rowIt(mat, j); 197 | typename MatrixType::StorageIndex startIdx = mat.cols(); 198 | if (rowIt) { // Necessary from the nature of the while loop below 199 | startIdx = rowIt.index(); 200 | } 201 | typename MatrixType::StorageIndex endIdx = startIdx; 202 | while (++rowIt) { endIdx = rowIt.index(); } // FixMe: Is there a better way? 203 | rowRanges.push_back(MatrixRowRange(j, startIdx, endIdx)); 204 | 205 | typename MatrixType::StorageIndex bw = endIdx - startIdx + 1; 206 | if (bandWidths.find(startIdx) == bandWidths.end()) { 207 | bandWidths.insert(std::make_pair(startIdx, bw)); 208 | } 209 | else { 210 | if (bandWidths.at(startIdx) < bw) { 211 | bandWidths.at(startIdx) = bw; 212 | } 213 | } 214 | 215 | if (bandHeights.find(startIdx) == bandHeights.end()) { 216 | bandHeights.insert(std::make_pair(startIdx, 1)); 217 | } 218 | else { 219 | bandHeights.at(startIdx) += 1; 220 | } 221 | } 222 | 223 | // 2) Search for banded blocks (blocks of row sharing same/similar band) 224 | typename MatrixType::StorageIndex maxColStep = 0; 225 | for (typename MatrixType::StorageIndex j = 0; j < rowRanges.size() - 1; j++) { 226 | if ((rowRanges.at(j + 1).start - rowRanges.at(j).start) > maxColStep) { 227 | maxColStep = (rowRanges.at(j + 1).start - rowRanges.at(j).start); 228 | } 229 | } 230 | 231 | // And record the estimated block structure 232 | this->nonZeroQEstimate = 0; 233 | this->blockMap.clear(); 234 | this->blockOrder.clear(); 235 | typename MatrixType::StorageIndex rowIdx = 0; 236 | for (auto it = rowRanges.begin(); it != rowRanges.end(); ++it, rowIdx++) { 237 | // std::find is terribly slow for large arrays 238 | // assuming m_blockOrder is ordered, we can use binary_search 239 | // is m_blockOrder always ordered? can we always use binary_search??? 240 | if (!std::binary_search(this->blockOrder.begin(), this->blockOrder.end(), it->start)) { 241 | // If start is out of bounds, it means that this is a 0 block - ignore it 242 | if (it->start < mat.cols()) { 243 | this->blockOrder.push_back(it->start); 244 | this->blockMap.insert(std::make_pair(it->start, MatrixBlockInfo(rowIdx, it->start, bandHeights.at(it->start), bandWidths.at(it->start)))); 245 | this->nonZeroQEstimate += this->blockMap.at(it->start).numRows * this->blockMap.at(it->start).numRows; 246 | } 247 | } 248 | } 249 | 250 | // 3) Go through the estimated block structure 251 | // And merge several blocks together if needed/possible in order to form reasonably big banded blocks 252 | this->mergeBlocks(this->blockOrder, this->blockMap, maxColStep); 253 | } 254 | 255 | void fromBlockDiagonalPattern(const IndexType matRows, const IndexType matCols, 256 | const IndexType blockRows, const IndexType blockCols) { 257 | 258 | // 1) Set the block map based on block paramters passed ot this method 259 | IndexType numBlocks = matCols / blockCols; 260 | this->blockMap.clear(); 261 | this->blockOrder.clear(); 262 | this->nonZeroQEstimate = 0; 263 | IndexType rowIdx = 0; 264 | IndexType colIdx = 0; 265 | for (int i = 0; i < numBlocks; i++) { 266 | rowIdx = i * blockRows; 267 | colIdx = i * blockCols; 268 | this->blockOrder.push_back(colIdx); 269 | this->blockMap.insert(std::make_pair(colIdx, MatrixBlockInfo(rowIdx, colIdx, blockRows, blockCols))); 270 | this->nonZeroQEstimate += blockRows * blockRows; 271 | } 272 | } 273 | 274 | void fromBlockBandedPattern(const IndexType matRows, const IndexType matCols, 275 | const IndexType blockRows, const IndexType blockCols, const IndexType blockOverlap) { 276 | 277 | // 1) Set the block map based on block paramters passed ot this method 278 | IndexType maxColStep = blockCols - blockOverlap; 279 | IndexType numBlocks = matCols / maxColStep; 280 | this->blockMap.clear(); 281 | this->blockOrder.clear(); 282 | this->nonZeroQEstimate = 0; 283 | IndexType rowIdx = 0; 284 | IndexType colIdx = 0; 285 | for (int i = 0; i < numBlocks; i++) { 286 | rowIdx = i * blockRows; 287 | colIdx = i * maxColStep; 288 | this->blockOrder.push_back(colIdx); 289 | if (i < numBlocks - 1) { 290 | this->blockMap.insert(std::make_pair(colIdx, MatrixBlockInfo(rowIdx, colIdx, blockRows, blockCols))); 291 | } 292 | else { 293 | // Last block need to be treated separately (only block overlap - we're at matrix bound) 294 | this->blockMap.insert(std::make_pair(colIdx, MatrixBlockInfo(rowIdx, colIdx, blockRows, blockCols - blockOverlap))); 295 | } 296 | this->nonZeroQEstimate += blockRows * blockRows; 297 | } 298 | 299 | // 2) Go through the estimated block structure 300 | // And merge several blocks together if needed/possible in order to form reasonably big banded blocks 301 | this->mergeBlocks(this->blockOrder, this->blockMap, maxColStep); 302 | } 303 | 304 | private: 305 | /* 306 | * Going through a block map and looking for a possibility to merge several blocks together in order to obtain the desired block structure. 307 | */ 308 | template 309 | void mergeBlocks(typename MatrixBlockInfo::MapOrder &blOrder, typename MatrixBlockInfo::Map &blMap, const int maxColStep) { 310 | typename MatrixBlockInfo::Map newBlockMap; 311 | typename MatrixBlockInfo::MapOrder newBlockOrder; 312 | MatrixBlockInfo firstBlock; 313 | int currRows, currCols; 314 | 315 | /* 316 | * Analyze parameters of each block. 317 | * If the existing block already has the required shape, leave it as is. 318 | * Otherwise start merging it with the next blocks until the conditions are met. 319 | */ 320 | auto it = blOrder.begin(); 321 | for (; it != blOrder.end(); ++it) { 322 | MatrixBlockInfo currBlock = blMap.at(*it); 323 | 324 | /* 325 | * If there are already some new block recorded and the current block is column-wise contained in the last block, 326 | * just merge the current block with the last block 327 | */ 328 | if (!newBlockOrder.empty()) { 329 | MatrixBlockInfo lastBlock = newBlockMap[newBlockOrder.back()]; 330 | if (currBlock.idxCol + currBlock.numCols <= lastBlock.idxCol + lastBlock.numCols) { 331 | int numRows = lastBlock.numRows + currBlock.numRows; 332 | int numCols = lastBlock.numCols; 333 | newBlockMap[newBlockOrder.back()] = MatrixBlockInfo(lastBlock.idxRow, lastBlock.idxCol, numRows, numCols); 334 | 335 | continue; 336 | } 337 | } 338 | /* 339 | * Otherwise, proceed with the typical block processing 340 | */ 341 | if (firstBlock.numRows == 0) { // If the first block has 0 rows, it is not set, the current block will be the first 342 | firstBlock = blMap.at(*it); 343 | currRows = currBlock.numRows; 344 | currCols = currBlock.numCols; 345 | } 346 | else { // Otherwise we already have first block, estimate new merged block size 347 | currRows = currBlock.idxRow + currBlock.numRows - firstBlock.idxRow; 348 | currCols = currBlock.idxCol + currBlock.numCols - firstBlock.idxCol; 349 | } 350 | 351 | /* 352 | * If the conditions for a new block are met, create it 353 | * 1) Each block has to have a portrait shape 354 | * 2) Each block should be at least maxColStep columns wide 355 | * 3) If requested by user, template parameter SuggestedBlockCols says the desired approximate amount of columns per block 356 | */ 357 | if (currRows > currCols && currCols >= maxColStep && currCols >= SuggestedBlockCols) { 358 | newBlockOrder.push_back(firstBlock.idxCol); 359 | newBlockMap.insert(std::make_pair(firstBlock.idxCol, MatrixBlockInfo(firstBlock.idxRow, firstBlock.idxCol, currRows, currCols))); 360 | 361 | // Reset first block 362 | firstBlock = MatrixBlockInfo(); 363 | } 364 | } 365 | 366 | /* 367 | * If merging of the last block was not done and we are already at the end, merge the remainder with the last block in the new array. 368 | */ 369 | if (firstBlock.numRows != 0) { 370 | if (currRows > currCols && currCols >= maxColStep && currCols >= SuggestedBlockCols) { 371 | newBlockOrder.push_back(firstBlock.idxCol); 372 | newBlockMap.insert(std::make_pair(firstBlock.idxCol, MatrixBlockInfo(firstBlock.idxRow, firstBlock.idxCol, currRows, currCols))); 373 | } 374 | else { 375 | MatrixBlockInfo lastBlock = newBlockMap[newBlockOrder.back()]; 376 | int numRows = lastBlock.numRows + currRows; 377 | int numCols = firstBlock.idxCol + currCols - lastBlock.idxCol; 378 | newBlockMap[newBlockOrder.back()] = MatrixBlockInfo(lastBlock.idxRow, lastBlock.idxCol, numRows, numCols); 379 | } 380 | } 381 | 382 | // Save the final banded block structure 383 | blOrder = newBlockOrder; 384 | blMap = newBlockMap; 385 | } 386 | }; 387 | 388 | 389 | /* 390 | * General parallel for loop. 391 | * Expects functor which loops through some subset of the range, bi..ei 392 | * This means it can be efficient even when the workload inside the loop is small. 393 | * Number of threads is passed as a paramter: 394 | * nthread = 0 ... use multithreading with std::thread::hardware_concurrency() threads 395 | * nthread = 1 ... do not use multithreading, evaluate as normal function call 396 | * nthread >= 2 ... use multithreading with nthread threads 397 | */ 398 | template 399 | void parallel_for(const int bi, const int ei, const Functor &f, size_t nthreads = 0) { 400 | 401 | if (nthreads == 1) { 402 | /********************************* ST *****************************/ 403 | f(bi, ei); 404 | } 405 | else { 406 | if (nthreads == 0) 407 | nthreads = std::thread::hardware_concurrency(); 408 | 409 | /********************************* MT *****************************/ 410 | const size_t nloop = ei - bi; 411 | { 412 | std::vector threads(nthreads); 413 | for (int t = 0; t < nthreads; t++) { 414 | threads[t] = std::thread(f, bi + t*nloop / nthreads, bi + ((t + 1) == nthreads ? nloop : (t + 1)*nloop / nthreads)); 415 | } 416 | std::for_each(threads.begin(), threads.end(), [](std::thread& x) {x.join(); }); 417 | } 418 | } 419 | } 420 | 421 | } 422 | } 423 | 424 | #endif 425 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.0) 2 | 3 | add_executable (test-qrkit test-qrkit.cpp) 4 | add_executable (test-utils test-utils.cpp) 5 | 6 | target_link_libraries (test-utils pthread Eigen3::Eigen) 7 | target_link_libraries (test-qrkit pthread Eigen3::Eigen) 8 | -------------------------------------------------------------------------------- /test/test-qrkit.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of Eigen, a lightweight C++ template library 2 | // for linear algebra. 3 | // 4 | // Copyright (C) 2020 Jan Svoboda 5 | // Copyright (C) 2020 Andrew Fitzgibbon 6 | // 7 | // This Source Code Form is subject to the terms of the Mozilla 8 | // Public License v. 2.0. If a copy of the MPL was not distributed 9 | // with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 10 | 11 | 12 | // import basic and product tests for deprectaed DynamicSparseMatrix 13 | #define EIGEN_NO_DEPRECATED_WARNING 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "test.h" 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | using namespace Eigen; 28 | using namespace QRKit; 29 | 30 | typedef double Scalar; 31 | 32 | template 33 | class ColPivHouseholderQRWrapper : public ColPivHouseholderQR { 34 | public: 35 | // Need to define MatrixQType and MatrixRType for the sparse QR solvers 36 | typedef Matrix MatrixQType; 37 | typedef MatrixType MatrixRType; 38 | }; 39 | 40 | typedef SparseMatrix JacobianType; 41 | typedef SparseMatrix JacobianTypeRowMajor; 42 | typedef Matrix DenseMatrixType; 43 | typedef HouseholderQR> BandBlockQRSolver; 44 | typedef BandedBlockedSparseQR BandedBlockedQRSolver; 45 | 46 | typedef BandedBlockedQRSolver LeftSuperBlockSolver; 47 | typedef ColPivHouseholderQRWrapper RightSuperBlockSolver; 48 | typedef BlockAngularSparseQR BlockAngularQRSolver; 49 | typedef Matrix DenseMatrix7x2; 50 | typedef ColPivHouseholderQRWrapper DenseQRSolver; 51 | typedef BlockDiagonalSparseQR BlockDiagonalQRSolver; 52 | 53 | typedef BlockedThinDenseQR BlockedThinDenseSolver; 54 | typedef BlockedThinSparseQR BlockedThinSparseSolver; 55 | 56 | typedef BlockAngularSparseQR BlockAngularQRSolverDenseBlocked; 57 | typedef BlockAngularSparseQR BlockAngularQRSolverDenseBlockedSparse; 58 | 59 | 60 | /* 61 | * Generate block diagonal sparse matrix with overlapping diagonal blocks. 62 | */ 63 | void generate_overlapping_block_diagonal_matrix(const Eigen::Index numParams, const Eigen::Index numResiduals, JacobianType &spJ, bool permuteRows = true) { 64 | std::default_random_engine gen; 65 | std::uniform_real_distribution dist(0.5, 5.0); 66 | 67 | int stride = 7; 68 | std::vector > jvals(stride * numParams); 69 | for (int i = 0; i < numParams; i++) { 70 | for (int j = i * 2; j < (i * 2) + 2 && j < numParams; j++) { 71 | jvals.emplace_back(i * stride, j, dist(gen)); 72 | jvals.emplace_back(i * stride + 1, j, dist(gen)); 73 | jvals.emplace_back(i * stride + 2, j, dist(gen)); 74 | jvals.emplace_back(i * stride + 3, j, dist(gen)); 75 | jvals.emplace_back(i * stride + 4, j, dist(gen)); 76 | jvals.emplace_back(i * stride + 5, j, dist(gen)); 77 | jvals.emplace_back(i * stride + 6, j, dist(gen)); 78 | if (j < numParams - 2) { 79 | jvals.emplace_back(i * stride + 6, j + 2, dist(gen)); 80 | } 81 | } 82 | } 83 | 84 | spJ.resize(numResiduals, numParams); 85 | spJ.setZero(); 86 | spJ.setFromTriplets(jvals.begin(), jvals.end()); 87 | spJ.makeCompressed(); 88 | 89 | // Permute Jacobian rows (we want to see how our QR handles a general matrix) 90 | if (permuteRows) { 91 | PermutationMatrix perm(spJ.rows()); 92 | perm.setIdentity(); 93 | std::random_shuffle(perm.indices().data(), perm.indices().data() + perm.indices().size()); 94 | spJ = perm * spJ; 95 | } 96 | } 97 | 98 | /* 99 | * Generate block diagonal sparse matrix. 100 | */ 101 | void generate_block_diagonal_matrix(const Eigen::Index numParams, const Eigen::Index numResiduals, JacobianType &spJ, bool permuteRows = true) { 102 | std::default_random_engine gen; 103 | std::uniform_real_distribution dist(0.5, 5.0); 104 | 105 | int stride = 7; 106 | std::vector > jvals(stride * numParams); 107 | for (int i = 0; i < numParams; i++) { 108 | for (int j = i * 2; j < (i * 2) + 2 && j < numParams; j++) { 109 | jvals.emplace_back(i * stride, j, dist(gen)); 110 | jvals.emplace_back(i * stride + 1, j, dist(gen)); 111 | jvals.emplace_back(i * stride + 2, j, dist(gen)); 112 | jvals.emplace_back(i * stride + 3, j, dist(gen)); 113 | jvals.emplace_back(i * stride + 4, j, dist(gen)); 114 | jvals.emplace_back(i * stride + 5, j, dist(gen)); 115 | jvals.emplace_back(i * stride + 6, j, dist(gen)); 116 | } 117 | } 118 | 119 | spJ.resize(numResiduals, numParams); 120 | spJ.setZero(); 121 | spJ.setFromTriplets(jvals.begin(), jvals.end()); 122 | spJ.makeCompressed(); 123 | 124 | // Permute Jacobian rows (we want to see how our QR handles a general matrix) 125 | if (permuteRows) { 126 | PermutationMatrix perm(spJ.rows()); 127 | perm.setIdentity(); 128 | std::random_shuffle(perm.indices().data(), perm.indices().data() + perm.indices().size()); 129 | spJ = perm * spJ; 130 | } 131 | } 132 | /* 133 | * Generate block angular sparse matrix with overlapping diagonal blocks. 134 | */ 135 | void generate_block_angular_matrix(const Eigen::Index numParams, const Eigen::Index numAngularParams, const Eigen::Index numResiduals, JacobianType &spJ) { 136 | std::default_random_engine gen; 137 | std::uniform_real_distribution dist(0.5, 5.0); 138 | 139 | int stride = 7; 140 | std::vector > jvals(stride * numParams + numResiduals * numAngularParams); 141 | for (int i = 0; i < numParams; i++) { 142 | for (int j = i * 2; j < (i * 2) + 2 && j < numParams; j++) { 143 | jvals.emplace_back(i * stride, j, dist(gen)); 144 | jvals.emplace_back(i * stride + 1, j, dist(gen)); 145 | jvals.emplace_back(i * stride + 2, j, dist(gen)); 146 | jvals.emplace_back(i * stride + 3, j, dist(gen)); 147 | jvals.emplace_back(i * stride + 4, j, dist(gen)); 148 | jvals.emplace_back(i * stride + 5, j, dist(gen)); 149 | jvals.emplace_back(i * stride + 6, j, dist(gen)); 150 | if (j < numParams - 2) { 151 | jvals.emplace_back(i * stride + 6, j + 2, dist(gen)); 152 | } 153 | } 154 | } 155 | for (int i = 0; i < numResiduals; i++) { 156 | for (int j = 0; j < numAngularParams; j++) { 157 | jvals.emplace_back(i, numParams + j, dist(gen)); 158 | } 159 | } 160 | 161 | spJ.resize(numResiduals, numParams + numAngularParams); 162 | spJ.setZero(); 163 | spJ.setFromTriplets(jvals.begin(), jvals.end()); 164 | spJ.makeCompressed(); 165 | } 166 | 167 | bool test_block_diagonal(const JacobianType &spJ, const int nVecEvals = 10) { 168 | bool test_res = true; 169 | 170 | /* 171 | * Solve the problem using the block diagonal QR solver. 172 | */ 173 | BlockDiagonalQRSolver bdqr; 174 | // Convert sparse matrix into block diagonal 175 | SparseBlockDiagonal blkDiag; 176 | blkDiag.fromBlockDiagonalPattern(spJ, 7, 2); 177 | // 1) Factorization 178 | bdqr.compute(blkDiag); 179 | 180 | // 3) Test simple LS solving 181 | // Prepare the data 182 | Eigen::VectorXd bdqrXDense = Eigen::VectorXd::Random(spJ.cols()); 183 | Eigen::VectorXd bdqrVecDense = spJ * bdqrXDense; 184 | // Solve LS 185 | Eigen::VectorXd bdqrResDense; 186 | for (int i = 0; i < nVecEvals; i++) { 187 | bdqrResDense = bdqr.matrixQ().transpose() * bdqrVecDense;//slvrVec; 188 | } 189 | VectorXd bdqrSolved; 190 | for (int i = 0; i < nVecEvals; i++) { 191 | bdqrSolved = bdqr.matrixR().topLeftCorner(spJ.cols(), spJ.cols()).template triangularView().solve(bdqrResDense.head(spJ.cols())); 192 | } 193 | VectorXd bdqrSolvedBackperm = VectorXd::Zero(spJ.cols()); 194 | for (int i = 0; i < spJ.cols(); i++) { 195 | bdqrSolvedBackperm(bdqr.colsPermutation().indices().coeff(i)) = bdqrSolved(i); 196 | } 197 | // 4) Apply computed column reordering 198 | JacobianType spJPerm = (spJ * bdqr.colsPermutation()); 199 | 200 | // 5) Test results 201 | test_res &= VERIFY_IS_APPROX(bdqr.matrixQ() * bdqr.matrixR(), spJPerm); 202 | test_res &= VERIFY_IS_APPROX(bdqr.matrixQ().transpose() * spJPerm, bdqr.matrixR()); 203 | test_res &= VERIFY_IS_APPROX(bdqrXDense, bdqrSolvedBackperm); 204 | 205 | return test_res; 206 | } 207 | 208 | bool test_banded_blocked(const JacobianType &spJ, const int nVecEvals = 10) { 209 | bool test_res = true; 210 | 211 | // Auxiliary identity matrix (for later use) 212 | JacobianType I(spJ.rows(), spJ.rows()); 213 | I.setIdentity(); 214 | 215 | /* 216 | * Solve the problem using the banded blocked QR solver. 217 | */ 218 | BandedBlockedQRSolver slvr; 219 | 220 | // 1) Factorization 221 | slvr.compute(spJ); 222 | // Q * I 223 | JacobianType slvrQ(spJ.rows(), spJ.rows()); 224 | slvrQ = slvr.matrixQ() * I; 225 | // Q.T * I 226 | JacobianType slvrQt(spJ.rows(), spJ.rows()); 227 | slvrQt = slvr.matrixQ().transpose() * I; 228 | 229 | // 3) Test simple LS solving 230 | // Prepare the data 231 | Eigen::VectorXd slvrXDense = Eigen::VectorXd::Random(spJ.cols()); 232 | Eigen::VectorXd slvrVecDense = spJ * slvrXDense; 233 | // Solve LS 234 | Eigen::VectorXd slvrResDense; 235 | slvrVecDense = (slvr.rowsPermutation() * slvrVecDense); 236 | for (int i = 0; i < nVecEvals; i++) { 237 | slvrResDense = slvr.matrixQ().transpose() * slvrVecDense;//slvrVec; 238 | } 239 | VectorXd solved; 240 | for (int i = 0; i < nVecEvals; i++) { 241 | solved = slvr.matrixR().topLeftCorner(spJ.cols(), spJ.cols()).template triangularView().solve(slvrResDense.head(spJ.cols())); 242 | } 243 | VectorXd solvedBackperm = VectorXd::Zero(spJ.cols()); 244 | for (int i = 0; i < spJ.cols(); i++) { 245 | solvedBackperm(slvr.colsPermutation().indices().coeff(i)) = solved(i); 246 | } 247 | // 4) Apply computed row reordering 248 | JacobianType spJRowPerm = (slvr.rowsPermutation() * spJ); 249 | 250 | // 5) Test results 251 | test_res &= VERIFY_IS_APPROX(slvrQ * slvr.matrixR(), spJRowPerm); 252 | test_res &= VERIFY_IS_APPROX(slvrQ.transpose() * spJRowPerm, slvr.matrixR()); 253 | test_res &= VERIFY_IS_APPROX(slvrQt.transpose() * slvr.matrixR(), spJRowPerm); 254 | test_res &= VERIFY_IS_APPROX(slvrQt * spJRowPerm, slvr.matrixR()); 255 | test_res &= VERIFY_IS_APPROX(slvrXDense, solvedBackperm); 256 | 257 | return test_res; 258 | } 259 | 260 | bool test_block_angular(const JacobianType &spJ, const int numAngularParams, const int nVecEvals = 10) { 261 | bool test_res = true; 262 | 263 | // 6) Solve sparse block angular matrix 264 | // Factorize 265 | BlockAngularQRSolver baqr; 266 | JacobianType leftBlock = spJ.block(0, 0, spJ.rows(), spJ.cols() - numAngularParams); 267 | DenseMatrixType rightBlock = spJ.block(0, spJ.cols() - numAngularParams, spJ.rows(), numAngularParams); 268 | BlockMatrix1x2 blkAngular(leftBlock, rightBlock); 269 | baqr.compute(blkAngular); 270 | // Prepare the data 271 | Eigen::VectorXd baqrXDense = Eigen::VectorXd::Random(spJ.cols()); 272 | Eigen::VectorXd baqrVecDense = spJ * baqrXDense; 273 | // Apply row permutation before solving 274 | baqrVecDense = baqr.rowsPermutation() * baqrVecDense; 275 | // Solve LS 276 | Eigen::VectorXd baqrResDense; 277 | for (int i = 0; i < nVecEvals; i++) { 278 | baqrResDense = baqr.matrixQ().transpose() * baqrVecDense;//slvrVec; 279 | } 280 | VectorXd baqrSolved; 281 | for (int i = 0; i < nVecEvals; i++) { 282 | baqrSolved = baqr.matrixR().topLeftCorner(spJ.cols(), spJ.cols()).template triangularView().solve(baqrResDense.head(spJ.cols())); 283 | } 284 | VectorXd baqrSolvedBackperm = VectorXd::Zero(spJ.cols()); 285 | for (int i = 0; i < spJ.cols(); i++) { 286 | baqrSolvedBackperm(baqr.colsPermutation().indices().coeff(i)) = baqrSolved(i); 287 | } 288 | 289 | test_res &= VERIFY_IS_APPROX(baqrXDense, baqrSolvedBackperm); 290 | 291 | return test_res; 292 | } 293 | 294 | bool test_block_angular_denseblocked(const JacobianType &spJ, const int numAngularParams, const int nVecEvals = 10) { 295 | bool test_res = true; 296 | 297 | // 6) Solve sparse block angular matrix 298 | // Factorize 299 | BlockAngularQRSolverDenseBlocked baqr; 300 | int rightBlockCols = 384; 301 | JacobianType leftBlock = spJ.block(0, 0, spJ.rows(), spJ.cols() - numAngularParams); 302 | DenseMatrixType rightBlock = spJ.block(0, spJ.cols() - numAngularParams, spJ.rows(), numAngularParams); 303 | BlockMatrix1x2 blkAngular(leftBlock, rightBlock); 304 | baqr.compute(blkAngular); 305 | // Prepare the data 306 | Eigen::VectorXd baqrXDense = Eigen::VectorXd::Random(spJ.cols()); 307 | Eigen::VectorXd baqrVecDense = spJ * baqrXDense; 308 | // Apply row permutation before solving 309 | baqrVecDense = baqr.rowsPermutation() * baqrVecDense; 310 | // Solve LS 311 | Eigen::VectorXd baqrResDense; 312 | for (int i = 0; i < nVecEvals; i++) { 313 | baqrResDense = baqr.matrixQ().transpose() * baqrVecDense;//slvrVec; 314 | } 315 | VectorXd baqrSolved; 316 | for (int i = 0; i < nVecEvals; i++) { 317 | baqrSolved = baqr.matrixR().topLeftCorner(spJ.cols(), spJ.cols()).template triangularView().solve(baqrResDense.head(spJ.cols())); 318 | } 319 | VectorXd baqrSolvedBackperm = VectorXd::Zero(spJ.cols()); 320 | for (int i = 0; i < spJ.cols(); i++) { 321 | baqrSolvedBackperm(baqr.colsPermutation().indices().coeff(i)) = baqrSolved(i); 322 | } 323 | 324 | test_res &= VERIFY_IS_APPROX(baqrXDense, baqrSolvedBackperm); 325 | 326 | return test_res; 327 | } 328 | 329 | bool test_block_angular_denseblocked_sparse(const JacobianType &spJ, const int numAngularParams, const int nVecEvals = 10) { 330 | bool test_res = true; 331 | 332 | // 6) Solve sparse block angular matrix 333 | // Factorize 334 | BlockAngularQRSolverDenseBlockedSparse baqr; 335 | int rightBlockCols = 384; 336 | JacobianType leftBlock = spJ.block(0, 0, spJ.rows(), spJ.cols() - numAngularParams); 337 | JacobianType rightBlock = spJ.block(0, spJ.cols() - numAngularParams, spJ.rows(), numAngularParams); 338 | BlockMatrix1x2 blkAngular(leftBlock, rightBlock); 339 | baqr.compute(blkAngular); 340 | // Prepare the data 341 | Eigen::VectorXd baqrXDense = Eigen::VectorXd::Random(spJ.cols()); 342 | Eigen::VectorXd baqrVecDense = spJ * baqrXDense; 343 | // Apply row permutation before solving 344 | baqrVecDense = baqr.rowsPermutation() * baqrVecDense; 345 | // Solve LS 346 | Eigen::VectorXd baqrResDense; 347 | for (int i = 0; i < nVecEvals; i++) { 348 | baqrResDense = baqr.matrixQ().transpose() * baqrVecDense;//slvrVec; 349 | } 350 | VectorXd baqrSolved; 351 | for (int i = 0; i < nVecEvals; i++) { 352 | baqrSolved = baqr.matrixR().topLeftCorner(spJ.cols(), spJ.cols()).template triangularView().solve(baqrResDense.head(spJ.cols())); 353 | } 354 | VectorXd baqrSolvedBackperm = VectorXd::Zero(spJ.cols()); 355 | for (int i = 0; i < spJ.cols(); i++) { 356 | baqrSolvedBackperm(baqr.colsPermutation().indices().coeff(i)) = baqrSolved(i); 357 | } 358 | 359 | test_res &= VERIFY_IS_APPROX(baqrXDense, baqrSolvedBackperm); 360 | 361 | return test_res; 362 | } 363 | 364 | int main(int argc, char* argv[]) { 365 | /* 366 | * Set-up the problem to be solved 367 | */ 368 | // Problem size 369 | Eigen::Index numVars = 256; 370 | Eigen::Index numParams = numVars * 2; 371 | Eigen::Index numResiduals = numVars * 3 + numVars + numVars * 3; 372 | int nVecEvals = 10; 373 | 374 | // Generate the sparse matrix 375 | JacobianType spJ; 376 | generate_block_diagonal_matrix(numParams, numResiduals, spJ, false); 377 | RUN_TEST(test_block_diagonal(spJ), 0); 378 | 379 | // Generate the sparse matrix 380 | generate_block_diagonal_matrix(numParams, numResiduals, spJ, false); 381 | RUN_TEST(test_banded_blocked(spJ), 1); 382 | generate_overlapping_block_diagonal_matrix(numParams, numResiduals, spJ, false); 383 | RUN_TEST(test_banded_blocked(spJ), 2); 384 | generate_overlapping_block_diagonal_matrix(numParams, numResiduals, spJ, true); 385 | RUN_TEST(test_banded_blocked(spJ), 3); 386 | 387 | // Generate new input 388 | numVars = 1024; 389 | numParams = numVars * 2; 390 | numResiduals = numVars * 3 + numVars + numVars * 3; 391 | Eigen::Index numAngularParams = 384; // 128 control points 392 | 393 | // Generate the sparse matrix 394 | generate_block_angular_matrix(numParams, numAngularParams, numResiduals, spJ); 395 | RUN_TEST(test_block_angular(spJ, numAngularParams), 4); 396 | 397 | // Generate the sparse matrix 398 | generate_block_angular_matrix(numParams, numAngularParams, numResiduals, spJ); 399 | RUN_TEST(test_block_angular_denseblocked(spJ, numAngularParams), 5); 400 | 401 | // Generate the sparse matrix 402 | generate_block_angular_matrix(numParams, numAngularParams, numResiduals, spJ); 403 | RUN_TEST(test_block_angular_denseblocked_sparse(spJ, numAngularParams), 6); 404 | 405 | return 0; 406 | } 407 | -------------------------------------------------------------------------------- /test/test-utils.cpp: -------------------------------------------------------------------------------- 1 | // This file is part of Eigen, a lightweight C++ template library 2 | // for linear algebra. 3 | // 4 | // Copyright (C) 2020 Jan Svoboda 5 | // Copyright (C) 2020 Andrew Fitzgibbon 6 | // 7 | // This Source Code Form is subject to the terms of the Mozilla 8 | // Public License v. 2.0. If a copy of the MPL was not distributed 9 | // with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 10 | 11 | 12 | // import basic and product tests for deprectaed DynamicSparseMatrix 13 | #define EIGEN_NO_DEPRECATED_WARNING 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "test.h" 22 | 23 | #include 24 | #include 25 | 26 | using namespace Eigen; 27 | using namespace QRKit; 28 | 29 | typedef double Scalar; 30 | 31 | typedef SparseMatrix JacobianType; 32 | typedef SparseMatrix JacobianTypeRowMajor; 33 | typedef Matrix DenseVectorType; 34 | typedef Eigen::SparseVector SparseVec; 35 | 36 | /* 37 | * Generate block diagonal sparse matrix with overlapping diagonal blocks. 38 | */ 39 | void generate_overlapping_block_diagonal_matrix(const Eigen::Index numParams, const Eigen::Index numResiduals, JacobianType &spJ, bool permuteRows = true) { 40 | std::default_random_engine gen; 41 | std::uniform_real_distribution dist(0.5, 5.0); 42 | 43 | int stride = 7; 44 | std::vector > jvals(stride * numParams); 45 | for (int i = 0; i < numParams; i++) { 46 | for (int j = i * 2; j < (i * 2) + 2 && j < numParams; j++) { 47 | jvals.emplace_back(i * stride, j, dist(gen)); 48 | jvals.emplace_back(i * stride + 1, j, dist(gen)); 49 | jvals.emplace_back(i * stride + 2, j, dist(gen)); 50 | jvals.emplace_back(i * stride + 3, j, dist(gen)); 51 | jvals.emplace_back(i * stride + 4, j, dist(gen)); 52 | jvals.emplace_back(i * stride + 5, j, dist(gen)); 53 | jvals.emplace_back(i * stride + 6, j, dist(gen)); 54 | if (j < numParams - 2) { 55 | jvals.emplace_back(i * stride + 6, j + 2, dist(gen)); 56 | } 57 | } 58 | } 59 | 60 | spJ.resize(numResiduals, numParams); 61 | spJ.setZero(); 62 | spJ.setFromTriplets(jvals.begin(), jvals.end()); 63 | spJ.makeCompressed(); 64 | 65 | // Permute Jacobian rows (we want to see how our QR handles a general matrix) 66 | if (permuteRows) { 67 | PermutationMatrix perm(spJ.rows()); 68 | perm.setIdentity(); 69 | std::random_shuffle(perm.indices().data(), perm.indices().data() + perm.indices().size()); 70 | spJ = perm * spJ; 71 | } 72 | } 73 | 74 | /* 75 | * Generate block diagonal sparse matrix. 76 | */ 77 | void generate_block_diagonal_matrix(const Eigen::Index numParams, const Eigen::Index numResiduals, JacobianType &spJ, bool permuteRows = true) { 78 | std::default_random_engine gen; 79 | std::uniform_real_distribution dist(0.5, 5.0); 80 | 81 | int stride = 7; 82 | std::vector > jvals(stride * numParams); 83 | for (int i = 0; i < numParams; i++) { 84 | for (int j = i * 2; j < (i * 2) + 2 && j < numParams; j++) { 85 | jvals.emplace_back(i * stride, j, dist(gen)); 86 | jvals.emplace_back(i * stride + 1, j, dist(gen)); 87 | jvals.emplace_back(i * stride + 2, j, dist(gen)); 88 | jvals.emplace_back(i * stride + 3, j, dist(gen)); 89 | jvals.emplace_back(i * stride + 4, j, dist(gen)); 90 | jvals.emplace_back(i * stride + 5, j, dist(gen)); 91 | jvals.emplace_back(i * stride + 6, j, dist(gen)); 92 | } 93 | } 94 | 95 | spJ.resize(numResiduals, numParams); 96 | spJ.setZero(); 97 | spJ.setFromTriplets(jvals.begin(), jvals.end()); 98 | spJ.makeCompressed(); 99 | 100 | // Permute Jacobian rows (we want to see how our QR handles a general matrix) 101 | if (permuteRows) { 102 | PermutationMatrix perm(spJ.rows()); 103 | perm.setIdentity(); 104 | std::random_shuffle(perm.indices().data(), perm.indices().data() + perm.indices().size()); 105 | spJ = perm * spJ; 106 | } 107 | } 108 | 109 | /* 110 | * Generate block angular sparse matrix with overlapping diagonal blocks. 111 | */ 112 | void generate_block_angular_matrix(const Eigen::Index numParams, const Eigen::Index numAngularParams, const Eigen::Index numResiduals, JacobianType &spJ) { 113 | std::default_random_engine gen; 114 | std::uniform_real_distribution dist(0.5, 5.0); 115 | 116 | int stride = 7; 117 | std::vector > jvals(stride * numParams + numResiduals * numAngularParams); 118 | for (int i = 0; i < numParams; i++) { 119 | for (int j = i * 2; j < (i * 2) + 2 && j < numParams; j++) { 120 | jvals.emplace_back(i * stride, j, dist(gen)); 121 | jvals.emplace_back(i * stride + 1, j, dist(gen)); 122 | jvals.emplace_back(i * stride + 2, j, dist(gen)); 123 | jvals.emplace_back(i * stride + 3, j, dist(gen)); 124 | jvals.emplace_back(i * stride + 4, j, dist(gen)); 125 | jvals.emplace_back(i * stride + 5, j, dist(gen)); 126 | jvals.emplace_back(i * stride + 6, j, dist(gen)); 127 | if (j < numParams - 2) { 128 | jvals.emplace_back(i * stride + 6, j + 2, dist(gen)); 129 | } 130 | } 131 | } 132 | for (int i = 0; i < numResiduals; i++) { 133 | for (int j = 0; j < numAngularParams; j++) { 134 | jvals.emplace_back(i, numParams + j, dist(gen)); 135 | } 136 | } 137 | 138 | spJ.resize(numResiduals, numParams + numAngularParams); 139 | spJ.setZero(); 140 | spJ.setFromTriplets(jvals.begin(), jvals.end()); 141 | spJ.makeCompressed(); 142 | } 143 | 144 | 145 | void rowpermADiagLambda(const JacobianType &A, const Scalar lambda, JacobianType &outA) { 146 | const Index nParams = A.cols(); 147 | const Index nResiduals = A.rows(); 148 | 149 | // Rowpermute the diagonal lambdas into A 150 | // Always place lambda below the last element of each column 151 | PermutationMatrix rowPerm(nResiduals + nParams); 152 | Index currRow = 0; 153 | for (Index c = 0; c < nParams; c++) { 154 | JacobianType::InnerIterator colIt(A, c); 155 | Index lastNnzIdx = 0; 156 | if (colIt) { // Necessary from the nature of the while loop below 157 | lastNnzIdx = colIt.index(); 158 | } 159 | while (++colIt) { lastNnzIdx = colIt.index(); } 160 | 161 | // Don't permute the nnz elements in the column 162 | while (currRow <= lastNnzIdx + c) { 163 | rowPerm.indices()(currRow - c) = currRow; 164 | currRow++; 165 | } 166 | // Put current diagonal element on this position 167 | rowPerm.indices()(nResiduals + c) = currRow; 168 | currRow++; 169 | } 170 | 171 | // Create concatenation of the Jacobian with the diagonal matrix of lambdas 172 | JacobianTypeRowMajor I(nParams, nParams); 173 | I.setIdentity(); 174 | 175 | JacobianTypeRowMajor Arm(nResiduals + nParams, nParams); 176 | Arm.reserve(A.nonZeros() + nParams); 177 | Arm.topRows(nResiduals) = A; 178 | Arm.bottomRows(nParams) = I * std::sqrt(lambda); 179 | outA = rowPerm * Arm; 180 | } 181 | 182 | bool test_blockdiag_permuted(const JacobianType &mat) { 183 | bool test_res = true; 184 | 185 | // Looking for as-banded-as-possible structure in the matrix 186 | PermutationMatrix permMat; 187 | SparseQROrdering::AsBandedAsPossible abapOrdering; 188 | JacobianTypeRowMajor rmMat(mat); 189 | abapOrdering(rmMat, permMat); 190 | 191 | // Permute if permutation found 192 | if (abapOrdering.hasPermutation) { 193 | rmMat = permMat * rmMat; 194 | } 195 | 196 | SparseQRUtils::BlockBandedMatrixInfo bInfo; 197 | bInfo(rmMat); 198 | 199 | test_res &= VERIFY_IS_EQUAL(bInfo.blockOrder.size(), 256); // Expecting 256 blocks 200 | for (int i = 0; i < bInfo.blockOrder.size(); i++) { 201 | SparseQRUtils::BlockBandedMatrixInfo::MatrixBlockInfo bi = bInfo.blockMap[bInfo.blockOrder[i]]; 202 | test_res &= VERIFY_IS_EQUAL(bi.idxRow, i * 7); 203 | test_res &= VERIFY_IS_EQUAL(bi.idxCol, i * 2); 204 | test_res &= VERIFY_IS_EQUAL(bi.numRows, 7); 205 | test_res &= VERIFY_IS_EQUAL(bi.numCols, 2); 206 | } 207 | 208 | return test_res; 209 | } 210 | 211 | bool test_overlapping_permuted(const JacobianType &mat) { 212 | bool test_res = true; 213 | 214 | // Looking for as-banded-as-possible structure in the matrix 215 | PermutationMatrix permMat; 216 | SparseQROrdering::AsBandedAsPossible abapOrdering; 217 | JacobianTypeRowMajor rmMat(mat); 218 | abapOrdering(rmMat, permMat); 219 | 220 | // Permute if permutation found 221 | if (abapOrdering.hasPermutation) { 222 | rmMat = permMat * rmMat; 223 | } 224 | 225 | SparseQRUtils::BlockBandedMatrixInfo bInfo; 226 | bInfo(rmMat); 227 | 228 | test_res &= VERIFY_IS_EQUAL(bInfo.blockOrder.size(), 255); // Expecting 256 blocks 229 | for (int i = 0; i < bInfo.blockOrder.size(); i++) { 230 | SparseQRUtils::BlockBandedMatrixInfo::MatrixBlockInfo bi = bInfo.blockMap[bInfo.blockOrder[i]]; 231 | if (i < bInfo.blockOrder.size() - 1) { // Expecting block 7x4 232 | test_res &= VERIFY_IS_EQUAL(bi.idxRow, i * 7); 233 | test_res &= VERIFY_IS_EQUAL(bi.idxCol, i * 2); 234 | test_res &= VERIFY_IS_EQUAL(bi.numRows, 7); 235 | test_res &= VERIFY_IS_EQUAL(bi.numCols, 4); 236 | } else { // Expecting last block 14x4 237 | test_res &= VERIFY_IS_EQUAL(bi.idxRow, i * 7); 238 | test_res &= VERIFY_IS_EQUAL(bi.idxCol, i * 2); 239 | test_res &= VERIFY_IS_EQUAL(bi.numRows, 14); 240 | test_res &= VERIFY_IS_EQUAL(bi.numCols, 4); 241 | } 242 | } 243 | 244 | // For debugging purposes 245 | //std::cout << bInfo.blockOrder.size() << std::endl; 246 | //auto it = bInfo.blockOrder.begin(); 247 | //for (it; it != bInfo.blockOrder.end(); ++it) { 248 | // std::cout << "[" << bInfo.blockMap[*it].idxRow << ", " << bInfo.blockMap[*it].idxCol << "] = " << bInfo.blockMap[*it].numRows << " x " << bInfo.blockMap[*it].numCols << std::endl; 249 | //} 250 | 251 | return test_res; 252 | } 253 | 254 | bool test_blockdiag_vertperm_diag(const JacobianType &mat) { 255 | bool test_res = true; 256 | 257 | JacobianType matDiag; 258 | rowpermADiagLambda(mat, 1e-3, matDiag); 259 | 260 | JacobianTypeRowMajor rmMat(matDiag); 261 | SparseQRUtils::BlockBandedMatrixInfo bInfo; 262 | bInfo(rmMat); 263 | 264 | test_res &= VERIFY_IS_EQUAL(bInfo.blockOrder.size(), 256); // Expecting 256 blocks 265 | for (int i = 0; i < bInfo.blockOrder.size(); i++) { 266 | SparseQRUtils::BlockBandedMatrixInfo::MatrixBlockInfo bi = bInfo.blockMap[bInfo.blockOrder[i]]; 267 | test_res &= VERIFY_IS_EQUAL(bi.idxRow, i * 9); 268 | test_res &= VERIFY_IS_EQUAL(bi.idxCol, i * 2); 269 | test_res &= VERIFY_IS_EQUAL(bi.numRows, 9); 270 | test_res &= VERIFY_IS_EQUAL(bi.numCols, 2); 271 | } 272 | 273 | return test_res; 274 | } 275 | 276 | bool test_parallel_for(const JacobianType &mat) { 277 | typedef std::vector>> ResValsVector; 278 | 279 | bool test_res = true; 280 | 281 | for(int i = 2; i < 5; i++) { 282 | /********************************************************************************/ 283 | // Do it sequentially 284 | ResValsVector resVals(mat.cols()); 285 | Index numNonZeros = 0; 286 | SparseQRUtils::parallel_for(0, mat.cols(), [&](const int bi, const int ei) { 287 | // loop over all items 288 | for (int j = bi; j < ei; j++) 289 | { 290 | DenseVectorType resColJd = mat.col(j).toDense(); 291 | 292 | resColJd = 5.0 * resColJd; 293 | 294 | // Write the result back to j-th column of res 295 | SparseVec resColJ = resColJd.sparseView(); 296 | numNonZeros += resColJ.nonZeros(); 297 | resVals[j].reserve(resColJ.nonZeros()); 298 | for (SparseVec::InnerIterator it(resColJ); it; ++it) { 299 | resVals[j].push_back(std::make_pair(it.row(), it.value())); 300 | } 301 | } 302 | }, 0); 303 | // Form the output 304 | JacobianType res(mat.rows(), mat.cols()); 305 | res.reserve(numNonZeros); 306 | for (int j = 0; j < resVals.size(); j++) { 307 | res.startVec(j); 308 | for (auto it = resVals[j].begin(); it != resVals[j].end(); ++it) { 309 | res.insertBack(it->first, j) = it->second; 310 | } 311 | } 312 | // Don't forget to call finalize 313 | res.finalize(); 314 | 315 | /********************************************************************************/ 316 | // Do it parallelly 317 | resVals.clear(); 318 | resVals = ResValsVector(mat.cols()); 319 | numNonZeros = 0; 320 | SparseQRUtils::parallel_for(0, mat.cols(), [&](const int bi, const int ei) { 321 | // loop over all items 322 | for (int j = bi; j < ei; j++) 323 | { 324 | DenseVectorType resColJd = mat.col(j).toDense(); 325 | 326 | resColJd = 5.0 * resColJd; 327 | 328 | // Write the result back to j-th column of res 329 | SparseVec resColJ = resColJd.sparseView(); 330 | numNonZeros += resColJ.nonZeros(); 331 | resVals[j].reserve(resColJ.nonZeros()); 332 | for (SparseVec::InnerIterator it(resColJ); it; ++it) { 333 | resVals[j].push_back(std::make_pair(it.row(), it.value())); 334 | } 335 | } 336 | }, i); 337 | // Form the output 338 | JacobianType res2(mat.rows(), mat.cols()); 339 | res2.reserve(numNonZeros); 340 | for (int j = 0; j < resVals.size(); j++) { 341 | res2.startVec(j); 342 | for (auto it = resVals[j].begin(); it != resVals[j].end(); ++it) { 343 | res2.insertBack(it->first, j) = it->second; 344 | } 345 | } 346 | // Don't forget to call finalize 347 | res2.finalize(); 348 | 349 | /********************************************************************************/ 350 | // Check that result of sequential equals result of parallel 351 | test_res &= VERIFY_IS_APPROX((res - res2).cwiseAbs().sum(), 0.0); 352 | } 353 | 354 | return test_res; 355 | } 356 | 357 | int main(int argc, char *argv[]) { 358 | /* 359 | * Set-up the problem to be solved 360 | */ 361 | // Problem size 362 | Eigen::Index numVars = 256; 363 | Eigen::Index numParams = numVars * 2; 364 | Eigen::Index numResiduals = numVars * 3 + numVars + numVars * 3; 365 | 366 | // Generate the 7x2 block diagonal pattern, permute and try to find the ordering back 367 | JacobianType spJ; 368 | generate_block_diagonal_matrix(numParams, numResiduals, spJ, true); 369 | RUN_TEST(test_blockdiag_permuted(spJ), 0); 370 | 371 | // Generate the 7x4 overlapping pattern, permute and try to find the ordering back 372 | generate_overlapping_block_diagonal_matrix(numParams, numResiduals, spJ, true); 373 | RUN_TEST(test_overlapping_permuted(spJ), 1); 374 | 375 | // Generate the 7x2 block diagonal pattern, and vertically concatenate it with a diagonal matrix 376 | // and rowpermute to form blocks 9x2 377 | generate_block_diagonal_matrix(numParams, numResiduals, spJ, false); 378 | RUN_TEST(test_blockdiag_vertperm_diag(spJ), 2); 379 | 380 | // Generate a random sparse matrix - for example block banded matrix (doesn't really matter) 381 | // and process it using parallel for 382 | generate_overlapping_block_diagonal_matrix(numParams, numResiduals, spJ, true); 383 | RUN_TEST(test_parallel_for(spJ), 3); 384 | 385 | return 0; 386 | } 387 | -------------------------------------------------------------------------------- /test/test.h: -------------------------------------------------------------------------------- 1 | // This file is part of Eigen, a lightweight C++ template library 2 | // for linear algebra. 3 | // 4 | // Copyright (C) 2020 Jan Svoboda 5 | // Copyright (C) 2020 Andrew Fitzgibbon 6 | // 7 | // This Source Code Form is subject to the terms of the Mozilla 8 | // Public License v. 2.0. If a copy of the MPL was not distributed 9 | // with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 10 | 11 | #ifndef TEST_H 12 | #define TEST_H 13 | 14 | #include 15 | 16 | // Running a test 17 | #define RUN_TEST(test_fn, test_num) std::cout << "Running test " << test_num << "..." << std::endl; \ 18 | if(test_fn) { \ 19 | std::cout << "Passed." << std::endl; \ 20 | } else { \ 21 | std::cout << "Failed." << std::endl; \ 22 | } 23 | 24 | // Evaluating results of a test 25 | #define VERIFY_IS_APPROX(x, y) (call_verify_is_approx(x, y)) 26 | #define VERIFY_IS_EQUAL(x, y) (call_verify_is_equal(x, y)) 27 | 28 | // Define precisions for different types 29 | template inline typename Eigen::NumTraits::Real test_precision() { return Eigen::NumTraits::dummy_precision(); } 30 | template<> inline float test_precision() { return 1e-3f; } 31 | template<> inline double test_precision() { return 1e-6; } 32 | template<> inline long double test_precision() { return 1e-6l; } 33 | template<> inline float test_precision >() { return test_precision(); } 34 | template<> inline double test_precision >() { return test_precision(); } 35 | template<> inline long double test_precision >() { return test_precision(); } 36 | 37 | // Verify-is-approx function overloads 38 | inline bool verify_is_approx(const short &x, const short &y) { 39 | return Eigen::internal::isApprox(x, y, test_precision()); 40 | } 41 | inline bool verify_is_approx(const unsigned short &x, const unsigned short &y) { 42 | return Eigen::internal::isApprox(x, y, test_precision()); 43 | } 44 | inline bool verify_is_approx(const unsigned int &x, const unsigned int &y) { 45 | return Eigen::internal::isApprox(x, y, test_precision()); 46 | } 47 | inline bool verify_is_approx(const long &x, const long &y) { 48 | return Eigen::internal::isApprox(x, y, test_precision()); 49 | } 50 | inline bool verify_is_approx(const unsigned long& x, const unsigned long& y) { 51 | return Eigen::internal::isApprox(x, y, test_precision()); 52 | } 53 | inline bool verify_is_approx(const int &x, const int &y) { 54 | return Eigen::internal::isApprox(x, y, test_precision()); 55 | } 56 | inline bool verify_is_approx(const float &x, const float &y) { 57 | return Eigen::internal::isApprox(x, y, test_precision()); 58 | } 59 | inline bool verify_is_approx(const double &x, const double &y) { 60 | return Eigen::internal::isApprox(x, y, test_precision()); 61 | } 62 | template 63 | inline bool verify_is_approx(const T1 &x, const T2 &y, typename T1::Scalar* = 0) { 64 | return x.isApprox(y, test_precision()); 65 | } 66 | 67 | template 68 | inline bool call_verify_is_approx(const T1& x, const T2& y) { 69 | bool ret = verify_is_approx(x, y); 70 | if (!ret) { 71 | std::cerr << "Difference too large wrt tolerance!" << std::endl; // << get_test_precision(a) << ", relative error is: " << test_relative_error(a, b) << std::endl; 72 | } 73 | return ret; 74 | } 75 | 76 | 77 | // Verify is equal 78 | template 79 | bool call_verify_is_equal(const T1 &actual, const T2 &expected, bool expect_equal = true); 80 | template 81 | bool call_verify_is_equal(const T1 &actual, const T2 &expected, bool expect_equal) { 82 | if ((actual == expected) == expect_equal) 83 | return true; 84 | 85 | // false: 86 | std::cerr 87 | << std::endl << " actual = " << actual 88 | << std::endl << " expected " << (expect_equal ? "= " : "!=") << expected << std::endl; 89 | 90 | return false; 91 | } 92 | 93 | #endif 94 | -------------------------------------------------------------------------------- /user.cmake.in: -------------------------------------------------------------------------------- 1 | # Copy this to user.cmake, updating the path to point to your Eigen install 2 | set(Eigen3_DIR "/path/to/my/eigen/") --------------------------------------------------------------------------------