blob: 7c523d399e1e4fb3284b6e0b1df3a41497dee5a5 [file] [log] [blame]
Austin Schuh70cc9552019-01-21 19:46:48 -08001// Ceres Solver - A fast non-linear least squares minimizer
2// Copyright 2015 Google Inc. All rights reserved.
3// http://ceres-solver.org/
4//
5// Redistribution and use in source and binary forms, with or without
6// modification, are permitted provided that the following conditions are met:
7//
8// * Redistributions of source code must retain the above copyright notice,
9// this list of conditions and the following disclaimer.
10// * Redistributions in binary form must reproduce the above copyright notice,
11// this list of conditions and the following disclaimer in the documentation
12// and/or other materials provided with the distribution.
13// * Neither the name of Google Inc. nor the names of its contributors may be
14// used to endorse or promote products derived from this software without
15// specific prior written permission.
16//
17// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27// POSSIBILITY OF SUCH DAMAGE.
28//
29// Author: sameeragarwal@google.com (Sameer Agarwal)
30
31#include "ceres/linear_least_squares_problems.h"
32
33#include <cstdio>
34#include <memory>
35#include <string>
36#include <vector>
37
38#include "ceres/block_sparse_matrix.h"
39#include "ceres/block_structure.h"
40#include "ceres/casts.h"
41#include "ceres/file.h"
42#include "ceres/stringprintf.h"
43#include "ceres/triplet_sparse_matrix.h"
44#include "ceres/types.h"
45#include "glog/logging.h"
46
47namespace ceres {
48namespace internal {
49
50using std::string;
51
52LinearLeastSquaresProblem* CreateLinearLeastSquaresProblemFromId(int id) {
53 switch (id) {
54 case 0:
55 return LinearLeastSquaresProblem0();
56 case 1:
57 return LinearLeastSquaresProblem1();
58 case 2:
59 return LinearLeastSquaresProblem2();
60 case 3:
61 return LinearLeastSquaresProblem3();
62 case 4:
63 return LinearLeastSquaresProblem4();
64 default:
65 LOG(FATAL) << "Unknown problem id requested " << id;
66 }
67 return NULL;
68}
69
70/*
71A = [1 2]
72 [3 4]
73 [6 -10]
74
75b = [ 8
76 18
77 -18]
78
79x = [2
80 3]
81
82D = [1
83 2]
84
85x_D = [1.78448275;
86 2.82327586;]
87 */
88LinearLeastSquaresProblem* LinearLeastSquaresProblem0() {
89 LinearLeastSquaresProblem* problem = new LinearLeastSquaresProblem;
90
91 TripletSparseMatrix* A = new TripletSparseMatrix(3, 2, 6);
92 problem->b.reset(new double[3]);
93 problem->D.reset(new double[2]);
94
95 problem->x.reset(new double[2]);
96 problem->x_D.reset(new double[2]);
97
98 int* Ai = A->mutable_rows();
99 int* Aj = A->mutable_cols();
100 double* Ax = A->mutable_values();
101
102 int counter = 0;
103 for (int i = 0; i < 3; ++i) {
104 for (int j = 0; j< 2; ++j) {
105 Ai[counter] = i;
106 Aj[counter] = j;
107 ++counter;
108 }
109 }
110
111 Ax[0] = 1.;
112 Ax[1] = 2.;
113 Ax[2] = 3.;
114 Ax[3] = 4.;
115 Ax[4] = 6;
116 Ax[5] = -10;
117 A->set_num_nonzeros(6);
118 problem->A.reset(A);
119
120 problem->b[0] = 8;
121 problem->b[1] = 18;
122 problem->b[2] = -18;
123
124 problem->x[0] = 2.0;
125 problem->x[1] = 3.0;
126
127 problem->D[0] = 1;
128 problem->D[1] = 2;
129
130 problem->x_D[0] = 1.78448275;
131 problem->x_D[1] = 2.82327586;
132 return problem;
133}
134
135
136/*
137 A = [1 0 | 2 0 0
138 3 0 | 0 4 0
139 0 5 | 0 0 6
140 0 7 | 8 0 0
141 0 9 | 1 0 0
142 0 0 | 1 1 1]
143
144 b = [0
145 1
146 2
147 3
148 4
149 5]
150
151 c = A'* b = [ 3
152 67
153 33
154 9
155 17]
156
157 A'A = [10 0 2 12 0
158 0 155 65 0 30
159 2 65 70 1 1
160 12 0 1 17 1
161 0 30 1 1 37]
162
163 S = [ 42.3419 -1.4000 -11.5806
164 -1.4000 2.6000 1.0000
165 11.5806 1.0000 31.1935]
166
167 r = [ 4.3032
168 5.4000
169 5.0323]
170
171 S\r = [ 0.2102
172 2.1367
173 0.1388]
174
175 A\b = [-2.3061
176 0.3172
177 0.2102
178 2.1367
179 0.1388]
180*/
181// The following two functions create a TripletSparseMatrix and a
182// BlockSparseMatrix version of this problem.
183
184// TripletSparseMatrix version.
185LinearLeastSquaresProblem* LinearLeastSquaresProblem1() {
186 int num_rows = 6;
187 int num_cols = 5;
188
189 LinearLeastSquaresProblem* problem = new LinearLeastSquaresProblem;
190 TripletSparseMatrix* A = new TripletSparseMatrix(num_rows,
191 num_cols,
192 num_rows * num_cols);
193 problem->b.reset(new double[num_rows]);
194 problem->D.reset(new double[num_cols]);
195 problem->num_eliminate_blocks = 2;
196
197 int* rows = A->mutable_rows();
198 int* cols = A->mutable_cols();
199 double* values = A->mutable_values();
200
201 int nnz = 0;
202
203 // Row 1
204 {
205 rows[nnz] = 0;
206 cols[nnz] = 0;
207 values[nnz++] = 1;
208
209 rows[nnz] = 0;
210 cols[nnz] = 2;
211 values[nnz++] = 2;
212 }
213
214 // Row 2
215 {
216 rows[nnz] = 1;
217 cols[nnz] = 0;
218 values[nnz++] = 3;
219
220 rows[nnz] = 1;
221 cols[nnz] = 3;
222 values[nnz++] = 4;
223 }
224
225 // Row 3
226 {
227 rows[nnz] = 2;
228 cols[nnz] = 1;
229 values[nnz++] = 5;
230
231 rows[nnz] = 2;
232 cols[nnz] = 4;
233 values[nnz++] = 6;
234 }
235
236 // Row 4
237 {
238 rows[nnz] = 3;
239 cols[nnz] = 1;
240 values[nnz++] = 7;
241
242 rows[nnz] = 3;
243 cols[nnz] = 2;
244 values[nnz++] = 8;
245 }
246
247 // Row 5
248 {
249 rows[nnz] = 4;
250 cols[nnz] = 1;
251 values[nnz++] = 9;
252
253 rows[nnz] = 4;
254 cols[nnz] = 2;
255 values[nnz++] = 1;
256 }
257
258 // Row 6
259 {
260 rows[nnz] = 5;
261 cols[nnz] = 2;
262 values[nnz++] = 1;
263
264 rows[nnz] = 5;
265 cols[nnz] = 3;
266 values[nnz++] = 1;
267
268 rows[nnz] = 5;
269 cols[nnz] = 4;
270 values[nnz++] = 1;
271 }
272
273 A->set_num_nonzeros(nnz);
274 CHECK(A->IsValid());
275
276 problem->A.reset(A);
277
278 for (int i = 0; i < num_cols; ++i) {
279 problem->D.get()[i] = 1;
280 }
281
282 for (int i = 0; i < num_rows; ++i) {
283 problem->b.get()[i] = i;
284 }
285
286 return problem;
287}
288
289// BlockSparseMatrix version
290LinearLeastSquaresProblem* LinearLeastSquaresProblem2() {
291 int num_rows = 6;
292 int num_cols = 5;
293
294 LinearLeastSquaresProblem* problem = new LinearLeastSquaresProblem;
295
296 problem->b.reset(new double[num_rows]);
297 problem->D.reset(new double[num_cols]);
298 problem->num_eliminate_blocks = 2;
299
300 CompressedRowBlockStructure* bs = new CompressedRowBlockStructure;
301 std::unique_ptr<double[]> values(new double[num_rows * num_cols]);
302
303 for (int c = 0; c < num_cols; ++c) {
304 bs->cols.push_back(Block());
305 bs->cols.back().size = 1;
306 bs->cols.back().position = c;
307 }
308
309 int nnz = 0;
310
311 // Row 1
312 {
313 values[nnz++] = 1;
314 values[nnz++] = 2;
315
316 bs->rows.push_back(CompressedRow());
317 CompressedRow& row = bs->rows.back();
318 row.block.size = 1;
319 row.block.position = 0;
320 row.cells.push_back(Cell(0, 0));
321 row.cells.push_back(Cell(2, 1));
322 }
323
324 // Row 2
325 {
326 values[nnz++] = 3;
327 values[nnz++] = 4;
328
329 bs->rows.push_back(CompressedRow());
330 CompressedRow& row = bs->rows.back();
331 row.block.size = 1;
332 row.block.position = 1;
333 row.cells.push_back(Cell(0, 2));
334 row.cells.push_back(Cell(3, 3));
335 }
336
337 // Row 3
338 {
339 values[nnz++] = 5;
340 values[nnz++] = 6;
341
342 bs->rows.push_back(CompressedRow());
343 CompressedRow& row = bs->rows.back();
344 row.block.size = 1;
345 row.block.position = 2;
346 row.cells.push_back(Cell(1, 4));
347 row.cells.push_back(Cell(4, 5));
348 }
349
350 // Row 4
351 {
352 values[nnz++] = 7;
353 values[nnz++] = 8;
354
355 bs->rows.push_back(CompressedRow());
356 CompressedRow& row = bs->rows.back();
357 row.block.size = 1;
358 row.block.position = 3;
359 row.cells.push_back(Cell(1, 6));
360 row.cells.push_back(Cell(2, 7));
361 }
362
363 // Row 5
364 {
365 values[nnz++] = 9;
366 values[nnz++] = 1;
367
368 bs->rows.push_back(CompressedRow());
369 CompressedRow& row = bs->rows.back();
370 row.block.size = 1;
371 row.block.position = 4;
372 row.cells.push_back(Cell(1, 8));
373 row.cells.push_back(Cell(2, 9));
374 }
375
376 // Row 6
377 {
378 values[nnz++] = 1;
379 values[nnz++] = 1;
380 values[nnz++] = 1;
381
382 bs->rows.push_back(CompressedRow());
383 CompressedRow& row = bs->rows.back();
384 row.block.size = 1;
385 row.block.position = 5;
386 row.cells.push_back(Cell(2, 10));
387 row.cells.push_back(Cell(3, 11));
388 row.cells.push_back(Cell(4, 12));
389 }
390
391 BlockSparseMatrix* A = new BlockSparseMatrix(bs);
392 memcpy(A->mutable_values(), values.get(), nnz * sizeof(*A->values()));
393
394 for (int i = 0; i < num_cols; ++i) {
395 problem->D.get()[i] = 1;
396 }
397
398 for (int i = 0; i < num_rows; ++i) {
399 problem->b.get()[i] = i;
400 }
401
402 problem->A.reset(A);
403
404 return problem;
405}
406
407
408/*
409 A = [1 0
410 3 0
411 0 5
412 0 7
413 0 9
414 0 0]
415
416 b = [0
417 1
418 2
419 3
420 4
421 5]
422*/
423// BlockSparseMatrix version
424LinearLeastSquaresProblem* LinearLeastSquaresProblem3() {
425 int num_rows = 5;
426 int num_cols = 2;
427
428 LinearLeastSquaresProblem* problem = new LinearLeastSquaresProblem;
429
430 problem->b.reset(new double[num_rows]);
431 problem->D.reset(new double[num_cols]);
432 problem->num_eliminate_blocks = 2;
433
434 CompressedRowBlockStructure* bs = new CompressedRowBlockStructure;
435 std::unique_ptr<double[]> values(new double[num_rows * num_cols]);
436
437 for (int c = 0; c < num_cols; ++c) {
438 bs->cols.push_back(Block());
439 bs->cols.back().size = 1;
440 bs->cols.back().position = c;
441 }
442
443 int nnz = 0;
444
445 // Row 1
446 {
447 values[nnz++] = 1;
448 bs->rows.push_back(CompressedRow());
449 CompressedRow& row = bs->rows.back();
450 row.block.size = 1;
451 row.block.position = 0;
452 row.cells.push_back(Cell(0, 0));
453 }
454
455 // Row 2
456 {
457 values[nnz++] = 3;
458 bs->rows.push_back(CompressedRow());
459 CompressedRow& row = bs->rows.back();
460 row.block.size = 1;
461 row.block.position = 1;
462 row.cells.push_back(Cell(0, 1));
463 }
464
465 // Row 3
466 {
467 values[nnz++] = 5;
468 bs->rows.push_back(CompressedRow());
469 CompressedRow& row = bs->rows.back();
470 row.block.size = 1;
471 row.block.position = 2;
472 row.cells.push_back(Cell(1, 2));
473 }
474
475 // Row 4
476 {
477 values[nnz++] = 7;
478 bs->rows.push_back(CompressedRow());
479 CompressedRow& row = bs->rows.back();
480 row.block.size = 1;
481 row.block.position = 3;
482 row.cells.push_back(Cell(1, 3));
483 }
484
485 // Row 5
486 {
487 values[nnz++] = 9;
488 bs->rows.push_back(CompressedRow());
489 CompressedRow& row = bs->rows.back();
490 row.block.size = 1;
491 row.block.position = 4;
492 row.cells.push_back(Cell(1, 4));
493 }
494
495 BlockSparseMatrix* A = new BlockSparseMatrix(bs);
496 memcpy(A->mutable_values(), values.get(), nnz * sizeof(*A->values()));
497
498 for (int i = 0; i < num_cols; ++i) {
499 problem->D.get()[i] = 1;
500 }
501
502 for (int i = 0; i < num_rows; ++i) {
503 problem->b.get()[i] = i;
504 }
505
506 problem->A.reset(A);
507
508 return problem;
509}
510
511/*
512 A = [1 2 0 0 0 1 1
513 1 4 0 0 0 5 6
514 0 0 9 0 0 3 1]
515
516 b = [0
517 1
518 2]
519*/
520// BlockSparseMatrix version
521//
522// This problem has the unique property that it has two different
523// sized f-blocks, but only one of them occurs in the rows involving
524// the one e-block. So performing Schur elimination on this problem
525// tests the Schur Eliminator's ability to handle non-e-block rows
526// correctly when their structure does not conform to the static
527// structure determined by DetectStructure.
528//
529// NOTE: This problem is too small and rank deficient to be solved without
530// the diagonal regularization.
531LinearLeastSquaresProblem* LinearLeastSquaresProblem4() {
532 int num_rows = 3;
533 int num_cols = 7;
534
535 LinearLeastSquaresProblem* problem = new LinearLeastSquaresProblem;
536
537 problem->b.reset(new double[num_rows]);
538 problem->D.reset(new double[num_cols]);
539 problem->num_eliminate_blocks = 1;
540
541 CompressedRowBlockStructure* bs = new CompressedRowBlockStructure;
542 std::unique_ptr<double[]> values(new double[num_rows * num_cols]);
543
544 // Column block structure
545 bs->cols.push_back(Block());
546 bs->cols.back().size = 2;
547 bs->cols.back().position = 0;
548
549 bs->cols.push_back(Block());
550 bs->cols.back().size = 3;
551 bs->cols.back().position = 2;
552
553 bs->cols.push_back(Block());
554 bs->cols.back().size = 2;
555 bs->cols.back().position = 5;
556
557 int nnz = 0;
558
559 // Row 1 & 2
560 {
561 bs->rows.push_back(CompressedRow());
562 CompressedRow& row = bs->rows.back();
563 row.block.size = 2;
564 row.block.position = 0;
565
566 row.cells.push_back(Cell(0, nnz));
567 values[nnz++] = 1;
568 values[nnz++] = 2;
569 values[nnz++] = 1;
570 values[nnz++] = 4;
571
572 row.cells.push_back(Cell(2, nnz));
573 values[nnz++] = 1;
574 values[nnz++] = 1;
575 values[nnz++] = 5;
576 values[nnz++] = 6;
577 }
578
579 // Row 3
580 {
581 bs->rows.push_back(CompressedRow());
582 CompressedRow& row = bs->rows.back();
583 row.block.size = 1;
584 row.block.position = 2;
585
586 row.cells.push_back(Cell(1, nnz));
587 values[nnz++] = 9;
588 values[nnz++] = 0;
589 values[nnz++] = 0;
590
591 row.cells.push_back(Cell(2, nnz));
592 values[nnz++] = 3;
593 values[nnz++] = 1;
594 }
595
596 BlockSparseMatrix* A = new BlockSparseMatrix(bs);
597 memcpy(A->mutable_values(), values.get(), nnz * sizeof(*A->values()));
598
599 for (int i = 0; i < num_cols; ++i) {
600 problem->D.get()[i] = (i + 1) * 100;
601 }
602
603 for (int i = 0; i < num_rows; ++i) {
604 problem->b.get()[i] = i;
605 }
606
607 problem->A.reset(A);
608 return problem;
609}
610
611namespace {
612bool DumpLinearLeastSquaresProblemToConsole(const SparseMatrix* A,
613 const double* D,
614 const double* b,
615 const double* x,
616 int num_eliminate_blocks) {
617 CHECK(A != nullptr);
618 Matrix AA;
619 A->ToDenseMatrix(&AA);
620 LOG(INFO) << "A^T: \n" << AA.transpose();
621
622 if (D != NULL) {
623 LOG(INFO) << "A's appended diagonal:\n"
624 << ConstVectorRef(D, A->num_cols());
625 }
626
627 if (b != NULL) {
628 LOG(INFO) << "b: \n" << ConstVectorRef(b, A->num_rows());
629 }
630
631 if (x != NULL) {
632 LOG(INFO) << "x: \n" << ConstVectorRef(x, A->num_cols());
633 }
634 return true;
635}
636
637void WriteArrayToFileOrDie(const string& filename,
638 const double* x,
639 const int size) {
640 CHECK(x != nullptr);
641 VLOG(2) << "Writing array to: " << filename;
642 FILE* fptr = fopen(filename.c_str(), "w");
643 CHECK(fptr != nullptr);
644 for (int i = 0; i < size; ++i) {
645 fprintf(fptr, "%17f\n", x[i]);
646 }
647 fclose(fptr);
648}
649
650bool DumpLinearLeastSquaresProblemToTextFile(const string& filename_base,
651 const SparseMatrix* A,
652 const double* D,
653 const double* b,
654 const double* x,
655 int num_eliminate_blocks) {
656 CHECK(A != nullptr);
657 LOG(INFO) << "writing to: " << filename_base << "*";
658
659 string matlab_script;
660 StringAppendF(&matlab_script,
661 "function lsqp = load_trust_region_problem()\n");
662 StringAppendF(&matlab_script,
663 "lsqp.num_rows = %d;\n", A->num_rows());
664 StringAppendF(&matlab_script,
665 "lsqp.num_cols = %d;\n", A->num_cols());
666
667 {
668 string filename = filename_base + "_A.txt";
669 FILE* fptr = fopen(filename.c_str(), "w");
670 CHECK(fptr != nullptr);
671 A->ToTextFile(fptr);
672 fclose(fptr);
673 StringAppendF(&matlab_script,
674 "tmp = load('%s', '-ascii');\n", filename.c_str());
675 StringAppendF(
676 &matlab_script,
677 "lsqp.A = sparse(tmp(:, 1) + 1, tmp(:, 2) + 1, tmp(:, 3), %d, %d);\n",
678 A->num_rows(),
679 A->num_cols());
680 }
681
682
683 if (D != NULL) {
684 string filename = filename_base + "_D.txt";
685 WriteArrayToFileOrDie(filename, D, A->num_cols());
686 StringAppendF(&matlab_script,
687 "lsqp.D = load('%s', '-ascii');\n", filename.c_str());
688 }
689
690 if (b != NULL) {
691 string filename = filename_base + "_b.txt";
692 WriteArrayToFileOrDie(filename, b, A->num_rows());
693 StringAppendF(&matlab_script,
694 "lsqp.b = load('%s', '-ascii');\n", filename.c_str());
695 }
696
697 if (x != NULL) {
698 string filename = filename_base + "_x.txt";
699 WriteArrayToFileOrDie(filename, x, A->num_cols());
700 StringAppendF(&matlab_script,
701 "lsqp.x = load('%s', '-ascii');\n", filename.c_str());
702 }
703
704 string matlab_filename = filename_base + ".m";
705 WriteStringToFileOrDie(matlab_script, matlab_filename);
706 return true;
707}
708} // namespace
709
710bool DumpLinearLeastSquaresProblem(const string& filename_base,
711 DumpFormatType dump_format_type,
712 const SparseMatrix* A,
713 const double* D,
714 const double* b,
715 const double* x,
716 int num_eliminate_blocks) {
717 switch (dump_format_type) {
718 case CONSOLE:
719 return DumpLinearLeastSquaresProblemToConsole(A, D, b, x,
720 num_eliminate_blocks);
721 case TEXTFILE:
722 return DumpLinearLeastSquaresProblemToTextFile(filename_base,
723 A, D, b, x,
724 num_eliminate_blocks);
725 default:
726 LOG(FATAL) << "Unknown DumpFormatType " << dump_format_type;
727 }
728
729 return true;
730}
731
732} // namespace internal
733} // namespace ceres