comparison libinterp/dldfcn/symbfact.cc @ 21585:bad3ed83330d

symbfact.cc: Overhaul dldfcn. * symbfact.cc: Redo docstring. Eliminate nargout input validation. Improve input validation for TYP and MODE variables. Add label cleanup: and use goto statements to guarantee that memory allocated in cholmod library is cleaned up. Use std::max rather than hand-coded max routine. Add BIST tests.
author Rik <rik@octave.org>
date Mon, 04 Apr 2016 09:00:22 -0700
parents ad0599a0acc6
children d7a268e68e69
comparison
equal deleted inserted replaced
21584:ee1a009dd60f 21585:bad3ed83330d
39 #include "ovl.h" 39 #include "ovl.h"
40 #include "utils.h" 40 #include "utils.h"
41 41
42 DEFUN_DLD (symbfact, args, nargout, 42 DEFUN_DLD (symbfact, args, nargout,
43 "-*- texinfo -*-\n\ 43 "-*- texinfo -*-\n\
44 @deftypefn {} {[@var{count}, @var{h}, @var{parent}, @var{post}, @var{r}] =} symbfact (@var{S})\n\ 44 @deftypefn {} {[@var{count}, @var{h}, @var{parent}, @var{post}, @var{R}] =} symbfact (@var{S})\n\
45 @deftypefnx {} {[@dots{}] =} symbfact (@var{S}, @var{typ})\n\ 45 @deftypefnx {} {[@dots{}] =} symbfact (@var{S}, @var{typ})\n\
46 @deftypefnx {} {[@dots{}] =} symbfact (@var{S}, @var{typ}, @var{mode})\n\ 46 @deftypefnx {} {[@dots{}] =} symbfact (@var{S}, @var{typ}, @var{mode})\n\
47 \n\ 47 \n\
48 Perform a symbolic factorization analysis on the sparse matrix @var{S}.\n\ 48 Perform a symbolic factorization analysis of the sparse matrix @var{S}.\n\
49 \n\ 49 \n\
50 The input variables are\n\ 50 The input variables are\n\
51 \n\ 51 \n\
52 @table @var\n\ 52 @table @var\n\
53 @item S\n\ 53 @item S\n\
54 @var{S} is a complex or real sparse matrix.\n\ 54 @var{S} is a real or complex sparse matrix.\n\
55 \n\ 55 \n\
56 @item typ\n\ 56 @item typ\n\
57 Is the type of the factorization and can be one of\n\ 57 Is the type of the factorization and can be one of\n\
58 \n\ 58 \n\
59 @table @samp\n\ 59 @table @asis\n\
60 @item sym\n\ 60 @item @qcode{\"sym\"} (default)\n\
61 Factorize @var{S}. This is the default.\n\ 61 Factorize @var{S}. Assumes @var{S} is symmetric and uses the upper\n\
62 \n\ 62 triangular portion of the matrix.\n\
63 @item col\n\ 63 \n\
64 Factorize @code{@var{S}' * @var{S}}.\n\ 64 @item @qcode{\"col\"}\n\
65 \n\ 65 Factorize @tcode{@var{S}' * @var{S}}.\n\
66 @item row\n\ 66 \n\
67 @item @qcode{\"row\"}\n\
67 Factorize @tcode{@var{S} * @var{S}'}.\n\ 68 Factorize @tcode{@var{S} * @var{S}'}.\n\
68 \n\ 69 \n\
69 @item lo\n\ 70 @item @qcode{\"lo\"}\n\
70 Factorize @tcode{@var{S}'}\n\ 71 Factorize @tcode{@var{S}'}. Assumes @var{S} is symmetric and uses the lower\n\
72 triangular portion of the matrix.\n\
71 @end table\n\ 73 @end table\n\
72 \n\ 74 \n\
73 @item mode\n\ 75 @item mode\n\
74 The default is to return the Cholesky@tie{}factorization for @var{r}, and if\n\ 76 When @var{mode} is unspecified return the Cholesky@tie{}factorization for\n\
75 @var{mode} is @qcode{'L'}, the conjugate transpose of the\n\ 77 @var{R}. If @var{mode} is @qcode{\"lower\"} or @qcode{\"L\"} then return\n\
76 Cholesky@tie{}factorization is returned. The conjugate transpose version is\n\ 78 the conjugate transpose @tcode{@var{R}'} which is a lower triangular factor.\n\
77 faster and uses less memory, but returns the same values for @var{count},\n\ 79 The conjugate transpose version is faster and uses less memory, but still\n\
78 @var{h}, @var{parent} and @var{post} outputs.\n\ 80 returns the same values for all other outputs: @var{count}, @var{h},\n\
81 @var{parent}, and @var{post}.\n\
79 @end table\n\ 82 @end table\n\
80 \n\ 83 \n\
81 The output variables are\n\ 84 The output variables are:\n\
82 \n\ 85 \n\
83 @table @var\n\ 86 @table @var\n\
84 @item count\n\ 87 @item count\n\
85 The row counts of the Cholesky@tie{}factorization as determined by\n\ 88 The row counts of the Cholesky@tie{}factorization as determined by\n\
86 @var{typ}.\n\ 89 @var{typ}. The computational difficulty of performing the true\n\
90 factorization using @code{chol} is @code{sum (@var{count} .^ 2)}.\n\
87 \n\ 91 \n\
88 @item h\n\ 92 @item h\n\
89 The height of the elimination tree.\n\ 93 The height of the elimination tree.\n\
90 \n\ 94 \n\
91 @item parent\n\ 95 @item parent\n\
92 The elimination tree itself.\n\ 96 The elimination tree itself.\n\
93 \n\ 97 \n\
94 @item post\n\ 98 @item post\n\
95 A sparse boolean matrix whose structure is that of the Cholesky\n\ 99 A sparse boolean matrix whose structure is that of the\n\
96 factorization as determined by @var{typ}.\n\ 100 Cholesky@tie{}factorization as determined by @var{typ}.\n\
97 @end table\n\ 101 @end table\n\
102 @seealso{chol, etree, treelayout}\n\
98 @end deftypefn") 103 @end deftypefn")
99 { 104 {
100 #ifdef HAVE_CHOLMOD 105 #ifdef HAVE_CHOLMOD
101 106
102 int nargin = args.length (); 107 int nargin = args.length ();
103 108
104 if (nargin < 1 || nargin > 3 || nargout > 5) 109 if (nargin < 1 || nargin > 3)
105 print_usage (); 110 print_usage ();
106 111
107 octave_value_list retval; 112 octave_value_list retval;
108 113
109 cholmod_common Common;
110 cholmod_common *cm = &Common;
111 CHOLMOD_NAME(start) (cm);
112
113 double spu = octave_sparse_params::get_key ("spumoni");
114 if (spu == 0.)
115 {
116 cm->print = -1;
117 SUITESPARSE_ASSIGN_FPTR (printf_func, cm->print_function, 0);
118 }
119 else
120 {
121 cm->print = static_cast<int> (spu) + 2;
122 SUITESPARSE_ASSIGN_FPTR (printf_func, cm->print_function, &SparseCholPrint);
123 }
124
125 cm->error_handler = &SparseCholError;
126 SUITESPARSE_ASSIGN_FPTR2 (divcomplex_func, cm->complex_divide, divcomplex);
127 SUITESPARSE_ASSIGN_FPTR2 (hypot_func, cm->hypotenuse, hypot);
128
129 double dummy; 114 double dummy;
130 cholmod_sparse Astore; 115 cholmod_sparse Astore;
131 cholmod_sparse *A = &Astore; 116 cholmod_sparse *A = &Astore;
132 A->packed = true; 117 A->packed = true;
133 A->sorted = true; 118 A->sorted = true;
173 octave_idx_type coletree = false; 158 octave_idx_type coletree = false;
174 octave_idx_type n = A->nrow; 159 octave_idx_type n = A->nrow;
175 160
176 if (nargin > 1) 161 if (nargin > 1)
177 { 162 {
163 std::string str = args(1).xstring_value ("TYP must be a string");
164 // FIXME: The input validation could be improved to use strncmp
178 char ch; 165 char ch;
179 std::string str = args(1).string_value (); 166 ch = tolower (str[0]);
180 ch = tolower (str.c_str ()[0]); 167 if (ch == 'r') // 'row'
181 if (ch == 'r')
182 A->stype = 0; 168 A->stype = 0;
183 else if (ch == 'c') 169 else if (ch == 'c') // 'col'
184 { 170 {
185 n = A->ncol; 171 n = A->ncol;
186 coletree = true; 172 coletree = true;
187 A->stype = 0; 173 A->stype = 0;
188 } 174 }
189 else if (ch == 's') 175 else if (ch == 's') // 'sym' (default)
190 A->stype = 1; 176 A->stype = 1;
191 else if (ch == 's') 177 else if (ch == 'l') // 'lo'
192 A->stype = -1; 178 A->stype = -1;
193 else 179 else
194 error ("symbfact: unrecognized TYP in symbolic factorization"); 180 error ("symbfact: unrecognized TYP \"%s\"", str.c_str ());
181 }
182
183 if (nargin == 3)
184 {
185 std::string str = args(2).xstring_value ("MODE must be a string");
186 // FIXME: The input validation could be improved to use strncmp
187 char ch;
188 ch = toupper (str[0]);
189 if (ch != 'L')
190 error ("symbfact: unrecognized MODE \"%s\"", str.c_str ());
195 } 191 }
196 192
197 if (A->stype && A->nrow != A->ncol) 193 if (A->stype && A->nrow != A->ncol)
198 err_square_matrix_required ("symbfact", "S"); 194 err_square_matrix_required ("symbfact", "S");
199 195
201 OCTAVE_LOCAL_BUFFER (octave_idx_type, Post, n); 197 OCTAVE_LOCAL_BUFFER (octave_idx_type, Post, n);
202 OCTAVE_LOCAL_BUFFER (octave_idx_type, ColCount, n); 198 OCTAVE_LOCAL_BUFFER (octave_idx_type, ColCount, n);
203 OCTAVE_LOCAL_BUFFER (octave_idx_type, First, n); 199 OCTAVE_LOCAL_BUFFER (octave_idx_type, First, n);
204 OCTAVE_LOCAL_BUFFER (octave_idx_type, Level, n); 200 OCTAVE_LOCAL_BUFFER (octave_idx_type, Level, n);
205 201
202 cholmod_common Common;
203 cholmod_common *cm = &Common;
204 CHOLMOD_NAME(start) (cm);
205
206 double spu = octave_sparse_params::get_key ("spumoni");
207 if (spu == 0.)
208 {
209 cm->print = -1;
210 SUITESPARSE_ASSIGN_FPTR (printf_func, cm->print_function, 0);
211 }
212 else
213 {
214 cm->print = static_cast<int> (spu) + 2;
215 SUITESPARSE_ASSIGN_FPTR (printf_func, cm->print_function, &SparseCholPrint);
216 }
217
218 cm->error_handler = &SparseCholError;
219 SUITESPARSE_ASSIGN_FPTR2 (divcomplex_func, cm->complex_divide, divcomplex);
220 SUITESPARSE_ASSIGN_FPTR2 (hypot_func, cm->hypotenuse, hypot);
221
206 cholmod_sparse *F = CHOLMOD_NAME(transpose) (A, 0, cm); 222 cholmod_sparse *F = CHOLMOD_NAME(transpose) (A, 0, cm);
207 cholmod_sparse *Aup, *Alo; 223 cholmod_sparse *Aup, *Alo;
208 224
209 if (A->stype == 1 || coletree) 225 if (A->stype == 1 || coletree)
210 { 226 {
211 Aup = A ; 227 Aup = A;
212 Alo = F ; 228 Alo = F;
213 } 229 }
214 else 230 else
215 { 231 {
216 Aup = F ; 232 Aup = F;
217 Alo = A ; 233 Alo = A;
218 } 234 }
219 235
220 CHOLMOD_NAME(etree) (Aup, Parent, cm); 236 CHOLMOD_NAME(etree) (Aup, Parent, cm);
221 237
238 ColumnVector tmp (n); // Declaration must precede any goto cleanup.
239 std::string err_msg;
240
222 if (cm->status < CHOLMOD_OK) 241 if (cm->status < CHOLMOD_OK)
223 error ("symbfact: matrix corrupted"); 242 {
243 err_msg = "symbfact: matrix corrupted";
244 goto cleanup;
245 }
224 246
225 if (CHOLMOD_NAME(postorder) (Parent, n, 0, Post, cm) != n) 247 if (CHOLMOD_NAME(postorder) (Parent, n, 0, Post, cm) != n)
226 error ("symbfact: postorder failed"); 248 {
249 err_msg = "symbfact: postorder failed";
250 goto cleanup;
251 }
227 252
228 CHOLMOD_NAME(rowcolcounts) (Alo, 0, 0, Parent, Post, 0, 253 CHOLMOD_NAME(rowcolcounts) (Alo, 0, 0, Parent, Post, 0,
229 ColCount, First, Level, cm); 254 ColCount, First, Level, cm);
230 255
231 if (cm->status < CHOLMOD_OK) 256 if (cm->status < CHOLMOD_OK)
232 error ("symbfact: matrix corrupted"); 257 {
258 err_msg = "symbfact: matrix corrupted";
259 goto cleanup;
260 }
233 261
234 if (nargout > 4) 262 if (nargout > 4)
235 { 263 {
236 cholmod_sparse *A1, *A2; 264 cholmod_sparse *A1, *A2;
237 265
255 A1 = A; 283 A1 = A;
256 A2 = F; 284 A2 = F;
257 } 285 }
258 286
259 // count the total number of entries in L 287 // count the total number of entries in L
260 octave_idx_type lnz = 0 ; 288 octave_idx_type lnz = 0;
261 for (octave_idx_type j = 0 ; j < n ; j++) 289 for (octave_idx_type j = 0 ; j < n ; j++)
262 lnz += ColCount[j]; 290 lnz += ColCount[j];
263
264 291
265 // allocate the output matrix L (pattern-only) 292 // allocate the output matrix L (pattern-only)
266 SparseBoolMatrix L (n, n, lnz); 293 SparseBoolMatrix L (n, n, lnz);
267 294
268 // initialize column pointers 295 // initialize column pointers
271 { 298 {
272 L.xcidx(j) = lnz; 299 L.xcidx(j) = lnz;
273 lnz += ColCount[j]; 300 lnz += ColCount[j];
274 } 301 }
275 L.xcidx(n) = lnz; 302 L.xcidx(n) = lnz;
276
277 303
278 // create a copy of the column pointers 304 // create a copy of the column pointers
279 octave_idx_type *W = First; 305 octave_idx_type *W = First;
280 for (octave_idx_type j = 0 ; j < n ; j++) 306 for (octave_idx_type j = 0 ; j < n ; j++)
281 W[j] = L.xcidx (j); 307 W[j] = L.xcidx (j);
282 308
283 // get workspace for computing one row of L 309 // get workspace for computing one row of L
284 cholmod_sparse *R 310 cholmod_sparse *R
285 = CHOLMOD_NAME (allocate_sparse) (n, 1, n, false, true, 311 = CHOLMOD_NAME(allocate_sparse) (n, 1, n, false, true,
286 0, CHOLMOD_PATTERN, cm); 312 0, CHOLMOD_PATTERN, cm);
287 octave_idx_type *Rp = static_cast<octave_idx_type *>(R->p); 313 octave_idx_type *Rp = static_cast<octave_idx_type *> (R->p);
288 octave_idx_type *Ri = static_cast<octave_idx_type *>(R->i); 314 octave_idx_type *Ri = static_cast<octave_idx_type *> (R->i);
289 315
290 // compute L one row at a time 316 // compute L one row at a time
291 for (octave_idx_type k = 0 ; k < n ; k++) 317 for (octave_idx_type k = 0 ; k < n ; k++)
292 { 318 {
293 // get the kth row of L and store in the columns of L 319 // get the kth row of L and store in the columns of L
294 CHOLMOD_NAME (row_subtree) (A1, A2, k, Parent, R, cm) ; 320 CHOLMOD_NAME(row_subtree) (A1, A2, k, Parent, R, cm);
295 for (octave_idx_type p = 0 ; p < Rp[1] ; p++) 321 for (octave_idx_type p = 0 ; p < Rp[1] ; p++)
296 L.xridx (W[Ri[p]]++) = k ; 322 L.xridx (W[Ri[p]]++) = k;
297 323
298 // add the diagonal entry 324 // add the diagonal entry
299 L.xridx (W[k]++) = k ; 325 L.xridx (W[k]++) = k;
300 } 326 }
301 327
302 // free workspace 328 // free workspace
303 CHOLMOD_NAME (free_sparse) (&R, cm) ; 329 CHOLMOD_NAME(free_sparse) (&R, cm);
304
305 330
306 // transpose L to get R, or leave as is 331 // transpose L to get R, or leave as is
307 if (nargin < 3) 332 if (nargin < 3)
308 L = L.transpose (); 333 L = L.transpose ();
309 334
312 L.xdata(p) = true; 337 L.xdata(p) = true;
313 338
314 retval(4) = L; 339 retval(4) = L;
315 } 340 }
316 341
317 ColumnVector tmp (n);
318 if (nargout > 3) 342 if (nargout > 3)
319 { 343 {
320 for (octave_idx_type i = 0; i < n; i++) 344 for (octave_idx_type i = 0; i < n; i++)
321 tmp(i) = Post[i] + 1; 345 tmp(i) = Post[i] + 1;
322 retval(3) = tmp; 346 retval(3) = tmp;
330 } 354 }
331 355
332 if (nargout > 1) 356 if (nargout > 1)
333 { 357 {
334 // compute the elimination tree height 358 // compute the elimination tree height
335 octave_idx_type height = 0 ; 359 octave_idx_type height = 0;
336 for (int i = 0 ; i < n ; i++) 360 for (int i = 0 ; i < n ; i++)
337 height = (height > Level[i] ? height : Level[i]); 361 height = std::max (height, Level[i]);
338 height++ ; 362 height++;
339 retval(1) = static_cast<double> (height); 363 retval(1) = static_cast<double> (height);
340 } 364 }
341 365
342 for (octave_idx_type i = 0; i < n; i++) 366 for (octave_idx_type i = 0; i < n; i++)
343 tmp(i) = ColCount[i]; 367 tmp(i) = ColCount[i];
344 retval(0) = tmp; 368 retval(0) = tmp;
369
370 cleanup:
371 CHOLMOD_NAME(free_sparse) (&F, cm);
372 CHOLMOD_NAME(finish) (cm);
373
374 if (! err_msg.empty ())
375 error (err_msg.c_str ());
345 376
346 return retval; 377 return retval;
347 378
348 #else 379 #else
349 err_disabled_feature ("symbfact", "CHOLMOD"); 380 err_disabled_feature ("symbfact", "CHOLMOD");
350 #endif 381 #endif
351 } 382 }
383
384 /*
385 %!testif HAVE_CHOLMOD
386 %! A = sparse (magic (3));
387 %! [count, h, parent, post, r] = symbfact (A);
388 %! assert (count, [3; 2; 1]);
389 %! assert (h, 3);
390 %! assert (parent, [2; 3; 0]);
391 %! assert (r, sparse (triu (true (3))));
392
393 %!testif HAVE_CHOLMOD
394 %! ## Test MODE "lower"
395 %! A = sparse (magic (3));
396 %! [~, ~, ~, ~, l] = symbfact (A, "sym", "lower");
397 %! assert (l, sparse (tril (true (3))));
398
399 %!testif HAVE_CHOLMOD
400 %! ## Bug #42587, singular matrix
401 %! A = sparse ([1 0 8;0 1 8;8 8 1]);
402 %! [count, h, parent, post, r] = symbfact (A);
403
404 ## Test input validation
405 %!testif HAVE_CHOLMOD
406 %! fail ("symbfact ()");
407 %! fail ("symbfact (1,2,3,4)");
408 %! fail ("symbfact ({1})", "wrong type argument 'cell'");
409 %! fail ("symbfact (sparse (1), {1})", "TYP must be a string");
410 %! fail ("symbfact (sparse (1), 'foobar')", 'unrecognized TYP "foobar"');
411 %! fail ("symbfact (sparse (1), 'sym', {'L'})", "MODE must be a string");
412 %! fail ('symbfact (sparse (1), "sym", "foobar")', 'unrecognized MODE "foobar"');
413 %! fail ("symbfact (sparse ([1, 2; 3, 4; 5, 6]))", "S must be a square matrix");
414
415 */