GNU Octave  6.2.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
colamd.cc
Go to the documentation of this file.
1 ////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (C) 1998-2021 The Octave Project Developers
4 //
5 // See the file COPYRIGHT.md in the top-level directory of this
6 // distribution or <https://octave.org/copyright/>.
7 //
8 // This file is part of Octave.
9 //
10 // Octave is free software: you can redistribute it and/or modify it
11 // under the terms of the GNU General Public License as published by
12 // the Free Software Foundation, either version 3 of the License, or
13 // (at your option) any later version.
14 //
15 // Octave is distributed in the hope that it will be useful, but
16 // WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 // GNU General Public License for more details.
19 //
20 // You should have received a copy of the GNU General Public License
21 // along with Octave; see the file COPYING. If not, see
22 // <https://www.gnu.org/licenses/>.
23 //
24 ////////////////////////////////////////////////////////////////////////
25 
26 // This is the octave interface to colamd, which bore the copyright given
27 // in the help of the functions.
28 
29 #if defined (HAVE_CONFIG_H)
30 # include "config.h"
31 #endif
32 
33 #include <cstdlib>
34 
35 #include <string>
36 
37 #include "CSparse.h"
38 #include "dNDArray.h"
39 #include "dSparse.h"
40 #include "oct-locbuf.h"
41 #include "oct-sparse.h"
42 
43 #include "defun.h"
44 #include "error.h"
45 #include "errwarn.h"
46 #include "ovl.h"
47 #include "pager.h"
48 
49 // The symmetric column elimination tree code take from the Davis LDL code.
50 // Copyright given elsewhere in this file.
51 static void
52 symetree (const octave_idx_type *ridx, const octave_idx_type *cidx,
54 {
56  OCTAVE_LOCAL_BUFFER (octave_idx_type, Pinv, (P ? n : 0));
57  if (P)
58  // If P is present then compute Pinv, the inverse of P
59  for (octave_idx_type k = 0 ; k < n ; k++)
60  Pinv[P[k]] = k;
61 
62  for (octave_idx_type k = 0 ; k < n ; k++)
63  {
64  // L(k,:) pattern: all nodes reachable in etree from nz in A(0:k-1,k)
65  Parent[k] = n ; // parent of k is not yet known
66  Flag[k] = k ; // mark node k as visited
67  octave_idx_type kk = (P ? P[k] : k); // kth original, or permuted, column
68  octave_idx_type p2 = cidx[kk+1];
69  for (octave_idx_type p = cidx[kk] ; p < p2 ; p++)
70  {
71  // A (i,k) is nonzero (original or permuted A)
72  octave_idx_type i = (P ? Pinv[ridx[p]] : ridx[p]);
73  if (i < k)
74  {
75  // follow path from i to root of etree, stop at flagged node
76  for ( ; Flag[i] != k ; i = Parent[i])
77  {
78  // find parent of i if not yet determined
79  if (Parent[i] == n)
80  Parent[i] = k;
81  Flag[i] = k ; // mark i as visited
82  }
83  }
84  }
85  }
86 }
87 
88 // The elimination tree post-ordering code below is taken from SuperLU
89 static inline octave_idx_type
91 {
92  pp[i] = i;
93  return i;
94 }
95 
96 static inline octave_idx_type
98 {
99  pp[s] = t;
100  return t;
101 }
102 
103 static inline octave_idx_type
105 {
106  octave_idx_type p = pp[i];
107  octave_idx_type gp = pp[p];
108 
109  while (gp != p)
110  {
111  pp[i] = gp;
112  i = gp;
113  p = pp[i];
114  gp = pp[p];
115  }
116 
117  return p;
118 }
119 
120 static octave_idx_type
122  octave_idx_type *next_kid, octave_idx_type *post,
123  octave_idx_type postnum)
124 {
125  for (octave_idx_type w = first_kid[v]; w != -1; w = next_kid[w])
126  postnum = etdfs (w, first_kid, next_kid, post, postnum);
127 
128  post[postnum++] = v;
129 
130  return postnum;
131 }
132 
133 static void
135  octave_idx_type *post)
136 {
137  // Allocate storage for working arrays and results
138  OCTAVE_LOCAL_BUFFER (octave_idx_type, first_kid, n+1);
139  OCTAVE_LOCAL_BUFFER (octave_idx_type, next_kid, n+1);
140 
141  // Set up structure describing children
142  for (octave_idx_type v = 0; v <= n; first_kid[v++] = -1)
143  ; // do nothing
144 
145  for (octave_idx_type v = n-1; v >= 0; v--)
146  {
147  octave_idx_type dad = parent[v];
148  next_kid[v] = first_kid[dad];
149  first_kid[dad] = v;
150  }
151 
152  // Depth-first search from dummy root vertex #n
153  etdfs (n, first_kid, next_kid, post, 0);
154 }
155 
156 static void
157 coletree (const octave_idx_type *ridx, const octave_idx_type *colbeg,
158  octave_idx_type *colend, octave_idx_type *parent,
160 {
163  OCTAVE_LOCAL_BUFFER (octave_idx_type, firstcol, nr);
164 
165  // Compute firstcol[row] = first nonzero column in row
166  for (octave_idx_type row = 0; row < nr; firstcol[row++] = nc)
167  ; // do nothing
168 
169  for (octave_idx_type col = 0; col < nc; col++)
170  for (octave_idx_type p = colbeg[col]; p < colend[col]; p++)
171  {
172  octave_idx_type row = ridx[p];
173  if (firstcol[row] > col)
174  firstcol[row] = col;
175  }
176 
177  // Compute etree by Liu's algorithm for symmetric matrices,
178  // except use (firstcol[r],c) in place of an edge (r,c) of A.
179  // Thus each row clique in A'*A is replaced by a star
180  // centered at its first vertex, which has the same fill.
181  for (octave_idx_type col = 0; col < nc; col++)
182  {
183  octave_idx_type cset = make_set (col, pp);
184  root[cset] = col;
185  parent[col] = nc;
186  for (octave_idx_type p = colbeg[col]; p < colend[col]; p++)
187  {
188  octave_idx_type row = firstcol[ridx[p]];
189  if (row >= col)
190  continue;
191  octave_idx_type rset = find (row, pp);
192  octave_idx_type rroot = root[rset];
193  if (rroot != col)
194  {
195  parent[rroot] = col;
196  cset = link (cset, rset, pp);
197  root[cset] = col;
198  }
199  }
200  }
201 }
202 
203 DEFUN (colamd, args, nargout,
204  doc: /* -*- texinfo -*-
205 @deftypefn {} {@var{p} =} colamd (@var{S})
206 @deftypefnx {} {@var{p} =} colamd (@var{S}, @var{knobs})
207 @deftypefnx {} {[@var{p}, @var{stats}] =} colamd (@var{S})
208 @deftypefnx {} {[@var{p}, @var{stats}] =} colamd (@var{S}, @var{knobs})
209 
210 Compute the column approximate minimum degree permutation.
211 
212 @code{@var{p} = colamd (@var{S})} returns the column approximate minimum
213 degree permutation vector for the sparse matrix @var{S}. For a
214 non-symmetric matrix @var{S}, @code{@var{S}(:,@var{p})} tends to have
215 sparser LU@tie{}factors than @var{S}. The Cholesky@tie{}factorization of
216 @code{@var{S}(:,@var{p})' * @var{S}(:,@var{p})} also tends to be sparser
217 than that of @code{@var{S}' * @var{S}}.
218 
219 @var{knobs} is an optional one- to three-element input vector. If @var{S}
220 is m-by-n, then rows with more than @code{max(16,@var{knobs}(1)*sqrt(n))}
221 entries are ignored. Columns with more than
222 @code{max (16,@var{knobs}(2)*sqrt(min(m,n)))} entries are removed prior to
223 ordering, and ordered last in the output permutation @var{p}. Only
224 completely dense rows or columns are removed if @code{@var{knobs}(1)} and
225 @code{@var{knobs}(2)} are < 0, respectively. If @code{@var{knobs}(3)} is
226 nonzero, @var{stats} and @var{knobs} are printed. The default is
227 @code{@var{knobs} = [10 10 0]}. Note that @var{knobs} differs from earlier
228 versions of colamd.
229 
230 @var{stats} is an optional 20-element output vector that provides data
231 about the ordering and the validity of the input matrix @var{S}. Ordering
232 statistics are in @code{@var{stats}(1:3)}. @code{@var{stats}(1)} and
233 @code{@var{stats}(2)} are the number of dense or empty rows and columns
234 ignored by @sc{colamd} and @code{@var{stats}(3)} is the number of garbage
235 collections performed on the internal data structure used by @sc{colamd}
236 (roughly of size @code{2.2 * nnz(@var{S}) + 4 * @var{m} + 7 * @var{n}}
237 integers).
238 
239 Octave built-in functions are intended to generate valid sparse matrices,
240 with no duplicate entries, with ascending row indices of the nonzeros
241 in each column, with a non-negative number of entries in each column (!)
242 and so on. If a matrix is invalid, then @sc{colamd} may or may not be able
243 to continue. If there are duplicate entries (a row index appears two or
244 more times in the same column) or if the row indices in a column are out
245 of order, then @sc{colamd} can correct these errors by ignoring the
246 duplicate entries and sorting each column of its internal copy of the
247 matrix @var{S} (the input matrix @var{S} is not repaired, however). If a
248 matrix is invalid in other ways then @sc{colamd} cannot continue, an error
249 message is printed, and no output arguments (@var{p} or @var{stats}) are
250 returned.
251 @sc{colamd} is thus a simple way to check a sparse matrix to see if it's
252 valid.
253 
254 @code{@var{stats}(4:7)} provide information if @sc{colamd} was able to
255 continue. The matrix is OK if @code{@var{stats}(4)} is zero, or 1 if
256 invalid. @code{@var{stats}(5)} is the rightmost column index that is
257 unsorted or contains duplicate entries, or zero if no such column exists.
258 @code{@var{stats}(6)} is the last seen duplicate or out-of-order row
259 index in the column index given by @code{@var{stats}(5)}, or zero if no
260 such row index exists. @code{@var{stats}(7)} is the number of duplicate
261 or out-of-order row indices. @code{@var{stats}(8:20)} is always zero in
262 the current version of @sc{colamd} (reserved for future use).
263 
264 The ordering is followed by a column elimination tree post-ordering.
265 
266 The authors of the code itself are @nospell{Stefan I. Larimore} and
267 @nospell{Timothy A. Davis}. The algorithm was developed in collaboration with
268 @nospell{John Gilbert}, Xerox PARC, and @nospell{Esmond Ng}, Oak Ridge National
269 Laboratory. (see @url{http://faculty.cse.tamu.edu/davis/suitesparse.html})
270 @seealso{colperm, symamd, ccolamd}
271 @end deftypefn */)
272 {
273 #if defined (HAVE_COLAMD)
274 
275  int nargin = args.length ();
276 
277  if (nargin < 1 || nargin > 2)
278  print_usage ();
279 
280  octave_value_list retval (nargout == 2 ? 2 : 1);
281  int spumoni = 0;
282 
283  // Get knobs
284  static_assert (COLAMD_KNOBS <= 40,
285  "colamd: # of COLAMD_KNOBS exceeded. Please report this to bugs.octave.org");
286  double knob_storage[COLAMD_KNOBS];
287  double *knobs = &knob_storage[0];
288  COLAMD_NAME (_set_defaults) (knobs);
289 
290  // Check for user-passed knobs
291  if (nargin == 2)
292  {
293  NDArray User_knobs = args(1).array_value ();
294  int nel_User_knobs = User_knobs.numel ();
295 
296  if (nel_User_knobs > 0)
297  knobs[COLAMD_DENSE_ROW] = User_knobs(0);
298  if (nel_User_knobs > 1)
299  knobs[COLAMD_DENSE_COL] = User_knobs(1);
300  if (nel_User_knobs > 2)
301  spumoni = static_cast<int> (User_knobs(2));
302 
303  // print knob settings if spumoni is set
304  if (spumoni)
305  {
306 
307  octave_stdout << "\ncolamd version " << COLAMD_MAIN_VERSION
308  << '.' << COLAMD_SUB_VERSION
309  << ", " << COLAMD_DATE << ":\n";
310 
311  if (knobs[COLAMD_DENSE_ROW] >= 0)
312  octave_stdout << "knobs(1): " << User_knobs (0)
313  << ", rows with > max (16,"
314  << knobs[COLAMD_DENSE_ROW] << "*sqrt (size(A,2)))"
315  << " entries removed\n";
316  else
317  octave_stdout << "knobs(1): " << User_knobs (0)
318  << ", only completely dense rows removed\n";
319 
320  if (knobs[COLAMD_DENSE_COL] >= 0)
321  octave_stdout << "knobs(2): " << User_knobs (1)
322  << ", cols with > max (16,"
323  << knobs[COLAMD_DENSE_COL] << "*sqrt (size(A)))"
324  << " entries removed\n";
325  else
326  octave_stdout << "knobs(2): " << User_knobs (1)
327  << ", only completely dense columns removed\n";
328 
329  octave_stdout << "knobs(3): " << User_knobs (2)
330  << ", statistics and knobs printed\n";
331 
332  }
333  }
334 
335  octave_idx_type n_row, n_col, nnz;
336  octave_idx_type *ridx, *cidx;
338  SparseMatrix sm;
339 
340  if (args(0).issparse ())
341  {
342  if (args(0).iscomplex ())
343  {
344  scm = args(0).sparse_complex_matrix_value ();
345  n_row = scm.rows ();
346  n_col = scm.cols ();
347  nnz = scm.nnz ();
348  ridx = scm.xridx ();
349  cidx = scm.xcidx ();
350  }
351  else
352  {
353  sm = args(0).sparse_matrix_value ();
354 
355  n_row = sm.rows ();
356  n_col = sm.cols ();
357  nnz = sm.nnz ();
358  ridx = sm.xridx ();
359  cidx = sm.xcidx ();
360  }
361  }
362  else
363  {
364  if (args(0).iscomplex ())
365  sm = SparseMatrix (real (args(0).complex_matrix_value ()));
366  else
367  sm = SparseMatrix (args(0).matrix_value ());
368 
369  n_row = sm.rows ();
370  n_col = sm.cols ();
371  nnz = sm.nnz ();
372  ridx = sm.xridx ();
373  cidx = sm.xcidx ();
374  }
375 
376  // Allocate workspace for colamd
378  for (octave_idx_type i = 0; i < n_col+1; i++)
379  p[i] = cidx[i];
380 
381  octave_idx_type Alen = COLAMD_NAME (_recommended) (nnz, n_row, n_col);
383  for (octave_idx_type i = 0; i < nnz; i++)
384  A[i] = ridx[i];
385 
386  // Order the columns (destroys A)
387  static_assert (COLAMD_STATS <= 40,
388  "colamd: # of COLAMD_STATS exceeded. Please report this to bugs.octave.org");
389  octave::suitesparse_integer stats_storage[COLAMD_STATS];
390  octave::suitesparse_integer *stats = &stats_storage[0];
391  if (! COLAMD_NAME () (n_row, n_col, Alen, A, p, knobs, stats))
392  {
393  COLAMD_NAME (_report)(stats);
394 
395  error ("colamd: internal error!");
396  }
397 
398  // column elimination tree post-ordering (reuse variables)
399  OCTAVE_LOCAL_BUFFER (octave_idx_type, colbeg, n_col + 1);
400  OCTAVE_LOCAL_BUFFER (octave_idx_type, colend, n_col + 1);
401  OCTAVE_LOCAL_BUFFER (octave_idx_type, etree, n_col + 1);
402 
403  for (octave_idx_type i = 0; i < n_col; i++)
404  {
405  colbeg[i] = cidx[p[i]];
406  colend[i] = cidx[p[i]+1];
407  }
408 
409  coletree (ridx, colbeg, colend, etree, n_row, n_col);
410 
411  // Calculate the tree post-ordering
412  tree_postorder (n_col, etree, colbeg);
413 
414  // return the permutation vector
415  NDArray out_perm (dim_vector (1, n_col));
416  for (octave_idx_type i = 0; i < n_col; i++)
417  out_perm(i) = p[colbeg[i]] + 1;
418 
419  retval(0) = out_perm;
420 
421  // print stats if spumoni > 0
422  if (spumoni > 0)
423  COLAMD_NAME (_report)(stats);
424 
425  // Return the stats vector
426  if (nargout == 2)
427  {
428  NDArray out_stats (dim_vector (1, COLAMD_STATS));
429  for (octave_idx_type i = 0 ; i < COLAMD_STATS ; i++)
430  out_stats(i) = stats[i];
431  retval(1) = out_stats;
432 
433  // fix stats (5) and (6), for 1-based information on
434  // jumbled matrix. note that this correction doesn't
435  // occur if symamd returns FALSE
436  out_stats(COLAMD_INFO1)++;
437  out_stats(COLAMD_INFO2)++;
438  }
439 
440  return retval;
441 
442 #else
443 
444  octave_unused_parameter (args);
445  octave_unused_parameter (nargout);
446 
447  err_disabled_feature ("colamd", "COLAMD");
448 
449 #endif
450 }
451 
452 DEFUN (symamd, args, nargout,
453  doc: /* -*- texinfo -*-
454 @deftypefn {} {@var{p} =} symamd (@var{S})
455 @deftypefnx {} {@var{p} =} symamd (@var{S}, @var{knobs})
456 @deftypefnx {} {[@var{p}, @var{stats}] =} symamd (@var{S})
457 @deftypefnx {} {[@var{p}, @var{stats}] =} symamd (@var{S}, @var{knobs})
458 
459 For a symmetric positive definite matrix @var{S}, returns the permutation
460 vector p such that @code{@var{S}(@var{p}, @var{p})} tends to have a
461 sparser Cholesky@tie{}factor than @var{S}.
462 
463 Sometimes @code{symamd} works well for symmetric indefinite matrices too.
464 The matrix @var{S} is assumed to be symmetric; only the strictly lower
465 triangular part is referenced. @var{S} must be square.
466 
467 @var{knobs} is an optional one- to two-element input vector. If @var{S} is
468 n-by-n, then rows and columns with more than
469 @code{max (16,@var{knobs}(1)*sqrt(n))} entries are removed prior to
470 ordering, and ordered last in the output permutation @var{p}. No
471 rows/columns are removed if @code{@var{knobs}(1) < 0}. If
472 @code{@var{knobs}(2)} is nonzero, @var{stats} and @var{knobs} are
473 printed. The default is @code{@var{knobs} = [10 0]}. Note that
474 @var{knobs} differs from earlier versions of @code{symamd}.
475 
476 @var{stats} is an optional 20-element output vector that provides data
477 about the ordering and the validity of the input matrix @var{S}. Ordering
478 statistics are in @code{@var{stats}(1:3)}.
479 @code{@var{stats}(1) = @var{stats}(2)} is the number of dense or empty rows
480 and columns ignored by SYMAMD and @code{@var{stats}(3)} is the number of
481 garbage collections performed on the internal data structure used by SYMAMD
482 (roughly of size @code{8.4 * nnz (tril (@var{S}, -1)) + 9 * @var{n}}
483 integers).
484 
485 Octave built-in functions are intended to generate valid sparse matrices,
486 with no duplicate entries, with ascending row indices of the nonzeros
487 in each column, with a non-negative number of entries in each column (!)
488 and so on. If a matrix is invalid, then SYMAMD may or may not be able
489 to continue. If there are duplicate entries (a row index appears two or
490 more times in the same column) or if the row indices in a column are out
491 of order, then SYMAMD can correct these errors by ignoring the duplicate
492 entries and sorting each column of its internal copy of the matrix S (the
493 input matrix S is not repaired, however). If a matrix is invalid in
494 other ways then SYMAMD cannot continue, an error message is printed, and
495 no output arguments (@var{p} or @var{stats}) are returned. SYMAMD is
496 thus a simple way to check a sparse matrix to see if it's valid.
497 
498 @code{@var{stats}(4:7)} provide information if SYMAMD was able to
499 continue. The matrix is OK if @code{@var{stats} (4)} is zero, or 1
500 if invalid. @code{@var{stats}(5)} is the rightmost column index that
501 is unsorted or contains duplicate entries, or zero if no such column
502 exists. @code{@var{stats}(6)} is the last seen duplicate or out-of-order
503 row index in the column index given by @code{@var{stats}(5)}, or zero
504 if no such row index exists. @code{@var{stats}(7)} is the number of
505 duplicate or out-of-order row indices. @code{@var{stats}(8:20)} is
506 always zero in the current version of SYMAMD (reserved for future use).
507 
508 The ordering is followed by a column elimination tree post-ordering.
509 
510 The authors of the code itself are @nospell{Stefan I. Larimore} and
511 @nospell{Timothy A. Davis}. The algorithm was developed in collaboration with
512 @nospell{John Gilbert}, Xerox PARC, and @nospell{Esmond Ng}, Oak Ridge National
513 Laboratory. (see @url{http://faculty.cse.tamu.edu/davis/suitesparse.html})
514 @seealso{colperm, colamd}
515 @end deftypefn */)
516 {
517 #if defined (HAVE_COLAMD)
518 
519  int nargin = args.length ();
520 
521  if (nargin < 1 || nargin > 2)
522  print_usage ();
523 
524  octave_value_list retval (nargin == 2 ? 2 : 1);
525  int spumoni = 0;
526 
527  // Get knobs
528  static_assert (COLAMD_KNOBS <= 40,
529  "symamd: # of COLAMD_KNOBS exceeded. Please report this to bugs.octave.org");
530  double knob_storage[COLAMD_KNOBS];
531  double *knobs = &knob_storage[0];
532  COLAMD_NAME (_set_defaults) (knobs);
533 
534  // Check for user-passed knobs
535  if (nargin == 2)
536  {
537  NDArray User_knobs = args(1).array_value ();
538  int nel_User_knobs = User_knobs.numel ();
539 
540  if (nel_User_knobs > 0)
541  knobs[COLAMD_DENSE_ROW] = User_knobs(COLAMD_DENSE_ROW);
542  if (nel_User_knobs > 1)
543  spumoni = static_cast<int> (User_knobs (1));
544  }
545 
546  // print knob settings if spumoni is set
547  if (spumoni > 0)
548  octave_stdout << "symamd: dense row/col fraction: "
549  << knobs[COLAMD_DENSE_ROW] << std::endl;
550 
551  octave_idx_type n_row, n_col;
552  octave_idx_type *ridx, *cidx;
553  SparseMatrix sm;
555 
556  if (args(0).issparse ())
557  {
558  if (args(0).iscomplex ())
559  {
560  scm = args(0).sparse_complex_matrix_value ();
561  n_row = scm.rows ();
562  n_col = scm.cols ();
563  ridx = scm.xridx ();
564  cidx = scm.xcidx ();
565  }
566  else
567  {
568  sm = args(0).sparse_matrix_value ();
569  n_row = sm.rows ();
570  n_col = sm.cols ();
571  ridx = sm.xridx ();
572  cidx = sm.xcidx ();
573  }
574  }
575  else
576  {
577  if (args(0).iscomplex ())
578  sm = SparseMatrix (real (args(0).complex_matrix_value ()));
579  else
580  sm = SparseMatrix (args(0).matrix_value ());
581 
582  n_row = sm.rows ();
583  n_col = sm.cols ();
584  ridx = sm.xridx ();
585  cidx = sm.xcidx ();
586  }
587 
588  if (n_row != n_col)
589  err_square_matrix_required ("symamd", "S");
590 
591  // Allocate workspace for symamd
592  OCTAVE_LOCAL_BUFFER (octave_idx_type, perm, n_col+1);
593  static_assert (COLAMD_STATS <= 40,
594  "symamd: # of COLAMD_STATS exceeded. Please report this to bugs.octave.org");
595  octave::suitesparse_integer stats_storage[COLAMD_STATS];
596  octave::suitesparse_integer *stats = &stats_storage[0];
597  if (! SYMAMD_NAME () (n_col, octave::to_suitesparse_intptr (ridx),
600  knobs, stats, &calloc, &free))
601  {
602  SYMAMD_NAME (_report)(stats);
603 
604  error ("symamd: internal error!");
605  }
606 
607  // column elimination tree post-ordering
608  OCTAVE_LOCAL_BUFFER (octave_idx_type, etree, n_col + 1);
609  symetree (ridx, cidx, etree, perm, n_col);
610 
611  // Calculate the tree post-ordering
612  OCTAVE_LOCAL_BUFFER (octave_idx_type, post, n_col + 1);
613  tree_postorder (n_col, etree, post);
614 
615  // return the permutation vector
616  NDArray out_perm (dim_vector (1, n_col));
617  for (octave_idx_type i = 0; i < n_col; i++)
618  out_perm(i) = perm[post[i]] + 1;
619 
620  retval(0) = out_perm;
621 
622  // print stats if spumoni > 0
623  if (spumoni > 0)
624  SYMAMD_NAME (_report)(stats);
625 
626  // Return the stats vector
627  if (nargout == 2)
628  {
629  NDArray out_stats (dim_vector (1, COLAMD_STATS));
630  for (octave_idx_type i = 0 ; i < COLAMD_STATS ; i++)
631  out_stats(i) = stats[i];
632  retval(1) = out_stats;
633 
634  // fix stats (5) and (6), for 1-based information on
635  // jumbled matrix. note that this correction doesn't
636  // occur if symamd returns FALSE
637  out_stats(COLAMD_INFO1)++;
638  out_stats(COLAMD_INFO2)++;
639  }
640 
641  return retval;
642 
643 #else
644 
645  octave_unused_parameter (args);
646  octave_unused_parameter (nargout);
647 
648  err_disabled_feature ("symamd", "COLAMD");
649 
650 #endif
651 }
652 
653 DEFUN (etree, args, nargout,
654  doc: /* -*- texinfo -*-
655 @deftypefn {} {@var{p} =} etree (@var{S})
656 @deftypefnx {} {@var{p} =} etree (@var{S}, @var{typ})
657 @deftypefnx {} {[@var{p}, @var{q}] =} etree (@var{S}, @var{typ})
658 
659 Return the elimination tree for the matrix @var{S}.
660 
661 By default @var{S} is assumed to be symmetric and the symmetric elimination
662 tree is returned. The argument @var{typ} controls whether a symmetric or
663 column elimination tree is returned. Valid values of @var{typ} are
664 @qcode{"sym"} or @qcode{"col"}, for symmetric or column elimination tree
665 respectively.
666 
667 Called with a second argument, @code{etree} also returns the postorder
668 permutations on the tree.
669 @end deftypefn */)
670 {
671  int nargin = args.length ();
672 
673  if (nargin < 1 || nargin > 2)
674  print_usage ();
675 
676  octave_value_list retval (nargout == 2 ? 2 : 1);
677 
678  octave_idx_type n_row = 0;
679  octave_idx_type n_col = 0;
680  octave_idx_type *ridx = nullptr;
681  octave_idx_type *cidx = nullptr;
682 
683  if (! args(0).issparse ())
684  error ("etree: S must be a sparse matrix");
685 
686  if (args(0).iscomplex ())
687  {
688  SparseComplexMatrix scm = args(0).sparse_complex_matrix_value ();
689 
690  n_row = scm.rows ();
691  n_col = scm.cols ();
692  ridx = scm.xridx ();
693  cidx = scm.xcidx ();
694  }
695  else
696  {
697  SparseMatrix sm = args(0).sparse_matrix_value ();
698 
699  n_row = sm.rows ();
700  n_col = sm.cols ();
701  ridx = sm.xridx ();
702  cidx = sm.xcidx ();
703  }
704 
705  bool is_sym = true;
706 
707  if (nargin == 2)
708  {
709  std::string str = args(1).xstring_value ("etree: TYP must be a string");
710  if (str.find ('C') == 0 || str.find ('c') == 0)
711  is_sym = false;
712  }
713 
714  // column elimination tree post-ordering (reuse variables)
715  OCTAVE_LOCAL_BUFFER (octave_idx_type, etree, n_col + 1);
716 
717  if (is_sym)
718  {
719  if (n_row != n_col)
720  error ("etree: S is marked as symmetric, but is not square");
721 
722  symetree (ridx, cidx, etree, nullptr, n_col);
723  }
724  else
725  {
726  OCTAVE_LOCAL_BUFFER (octave_idx_type, colbeg, n_col);
727  OCTAVE_LOCAL_BUFFER (octave_idx_type, colend, n_col);
728 
729  for (octave_idx_type i = 0; i < n_col; i++)
730  {
731  colbeg[i] = cidx[i];
732  colend[i] = cidx[i+1];
733  }
734 
735  coletree (ridx, colbeg, colend, etree, n_row, n_col);
736  }
737 
738  NDArray tree (dim_vector (1, n_col));
739  for (octave_idx_type i = 0; i < n_col; i++)
740  // We flag a root with n_col while Matlab does it with zero
741  // Convert for matlab compatible output
742  if (etree[i] == n_col)
743  tree(i) = 0;
744  else
745  tree(i) = etree[i] + 1;
746 
747  retval(0) = tree;
748 
749  if (nargout == 2)
750  {
751  // Calculate the tree post-ordering
752  OCTAVE_LOCAL_BUFFER (octave_idx_type, post, n_col + 1);
753  tree_postorder (n_col, etree, post);
754 
755  NDArray postorder (dim_vector (1, n_col));
756  for (octave_idx_type i = 0; i < n_col; i++)
757  postorder(i) = post[i] + 1;
758 
759  retval(1) = postorder;
760  }
761 
762  return retval;
763 }
764 
765 /*
766 %!assert (etree (speye (2)), [0, 0]);
767 %!assert (etree (gallery ("poisson", 16)), [2:256, 0]);
768 
769 %!error etree ()
770 %!error etree (1, 2, 3)
771 %!error <S must be a sparse matrix> etree ([1, 2; 3, 4])
772 %!error <TYP must be a string> etree (speye (2), 3)
773 %!error <is not square> etree (sprand (2, 4, .25))
774 */
octave_idx_type numel(void) const
Number of elements in the array.
Definition: Array.h:377
octave_idx_type cols(void) const
Definition: Sparse.h:251
octave_idx_type * xridx(void)
Definition: Sparse.h:485
octave_idx_type nnz(void) const
Actual number of nonzero terms.
Definition: Sparse.h:238
octave_idx_type rows(void) const
Definition: Sparse.h:250
octave_idx_type * xcidx(void)
Definition: Sparse.h:498
Vector representing the dimensions (size) of an Array.
Definition: dim-vector.h:95
static octave_idx_type etdfs(octave_idx_type v, octave_idx_type *first_kid, octave_idx_type *next_kid, octave_idx_type *post, octave_idx_type postnum)
Definition: colamd.cc:121
static void tree_postorder(octave_idx_type n, octave_idx_type *parent, octave_idx_type *post)
Definition: colamd.cc:134
static octave_idx_type make_set(octave_idx_type i, octave_idx_type *pp)
Definition: colamd.cc:90
static void coletree(const octave_idx_type *ridx, const octave_idx_type *colbeg, octave_idx_type *colend, octave_idx_type *parent, octave_idx_type nr, octave_idx_type nc)
Definition: colamd.cc:157
static octave_idx_type link(octave_idx_type s, octave_idx_type t, octave_idx_type *pp)
Definition: colamd.cc:97
static octave_idx_type find(octave_idx_type i, octave_idx_type *pp)
Definition: colamd.cc:104
static void symetree(const octave_idx_type *ridx, const octave_idx_type *cidx, octave_idx_type *Parent, octave_idx_type *P, octave_idx_type n)
Definition: colamd.cc:52
ColumnVector real(const ComplexColumnVector &a)
Definition: dColVector.cc:137
OCTINTERP_API void print_usage(void)
Definition: defun.cc:53
#define DEFUN(name, args_name, nargout_name, doc)
Macro to define a builtin function.
Definition: defun.h:56
void error(const char *fmt,...)
Definition: error.cc:968
void err_square_matrix_required(const char *fcn, const char *name)
Definition: errwarn.cc:122
void err_disabled_feature(const std::string &fcn, const std::string &feature, const std::string &pkg)
Definition: errwarn.cc:53
F77_RET_T const F77_INT F77_CMPLX * A
octave_idx_type n
Definition: mx-inlines.cc:753
std::complex< double > w(std::complex< double > z, double relerr=0)
int suitesparse_integer
Definition: oct-sparse.h:171
suitesparse_integer * to_suitesparse_intptr(octave_idx_type *i)
Definition: oct-sparse.cc:51
#define OCTAVE_LOCAL_BUFFER(T, buf, size)
Definition: oct-locbuf.h:44
void free(void *)
octave_value::octave_value(const Array< char > &chm, char type) return retval
Definition: ov.cc:811
#define octave_stdout
Definition: pager.h:313