Eigen  5.0.1
 
Loading...
Searching...
No Matches
Eigen_Colamd.h
1// // This file is part of Eigen, a lightweight C++ template library
2// for linear algebra.
3//
4// Copyright (C) 2012 Desire Nuentsa Wakam <desire.nuentsa_wakam@inria.fr>
5//
6// This Source Code Form is subject to the terms of the Mozilla
7// Public License v. 2.0. If a copy of the MPL was not distributed
8// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
9// SPDX-License-Identifier: MPL-2.0
10
11// This file is modified from the colamd/symamd library. The copyright is below
12
13// The authors of the code itself are Stefan I. Larimore and Timothy A.
14// Davis (davis@cise.ufl.edu), University of Florida. The algorithm was
15// developed in collaboration with John Gilbert, Xerox PARC, and Esmond
16// Ng, Oak Ridge National Laboratory.
17//
18// Date:
19//
20// September 8, 2003. Version 2.3.
21//
22// Acknowledgements:
23//
24// This work was supported by the National Science Foundation, under
25// grants DMS-9504974 and DMS-9803599.
26//
27// Notice:
28//
29// Copyright (c) 1998-2003 by the University of Florida.
30// All Rights Reserved.
31//
32// THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY
33// EXPRESSED OR IMPLIED. ANY USE IS AT YOUR OWN RISK.
34//
35// Permission is hereby granted to use, copy, modify, and/or distribute
36// this program, provided that the Copyright, this License, and the
37// Availability of the original version is retained on all copies and made
38// accessible to the end-user of any code or package that includes COLAMD
39// or any modified version of COLAMD.
40//
41// Availability:
42//
43// The colamd/symamd library is available at
44//
45// http://www.suitesparse.com
46
47#ifndef EIGEN_COLAMD_H
48#define EIGEN_COLAMD_H
49
50namespace Eigen {
51namespace internal {
52namespace Colamd {
53
54/* Ensure that debugging is turned off: */
55#ifndef COLAMD_NDEBUG
56#define COLAMD_NDEBUG
57#endif /* COLAMD_NDEBUG */
58
59/* ========================================================================== */
60/* === Knob and statistics definitions ====================================== */
61/* ========================================================================== */
62
63/* size of the knobs [ ] array. Only knobs [0..1] are currently used. */
64const int NKnobs = 20;
65
66/* number of output statistics. Only stats [0..6] are currently used. */
67const int NStats = 20;
68
69/* Indices into knobs and stats array. */
70enum KnobsStatsIndex {
71 /* knobs [0] and stats [0]: dense row knob and output statistic. */
72 DenseRow = 0,
73
74 /* knobs [1] and stats [1]: dense column knob and output statistic. */
75 DenseCol = 1,
76
77 /* stats [2]: memory defragmentation count output statistic */
78 DefragCount = 2,
79
80 /* stats [3]: colamd status: zero OK, > 0 warning or notice, < 0 error */
81 Status = 3,
82
83 /* stats [4..6]: error info, or info on jumbled columns */
84 Info1 = 4,
85 Info2 = 5,
86 Info3 = 6
87};
88
89/* error codes returned in stats [3]: */
90enum Status {
91 Ok = 0,
92 OkButJumbled = 1,
93 ErrorANotPresent = -1,
94 ErrorPNotPresent = -2,
95 ErrorNrowNegative = -3,
96 ErrorNcolNegative = -4,
97 ErrorNnzNegative = -5,
98 ErrorP0Nonzero = -6,
99 ErrorATooSmall = -7,
100 ErrorColLengthNegative = -8,
101 ErrorRowIndexOutOfBounds = -9,
102 ErrorOutOfMemory = -10,
103 ErrorInternalError = -999
104};
105/* ========================================================================== */
106/* === Definitions ========================================================== */
107/* ========================================================================== */
108
109template <typename IndexType>
110IndexType ones_complement(const IndexType r) {
111 return (-(r)-1);
112}
113
114/* -------------------------------------------------------------------------- */
115const int Empty = -1;
116
117/* Row and column status */
118enum RowColumnStatus { Alive = 0, Dead = -1 };
119
120/* Column status */
121enum ColumnStatus { DeadPrincipal = -1, DeadNonPrincipal = -2 };
122
123/* ========================================================================== */
124/* === Colamd reporting mechanism =========================================== */
125/* ========================================================================== */
126
127// == Row and Column structures ==
128template <typename IndexType>
129struct ColStructure {
130 IndexType start; /* index for A of first row in this column, or Dead */
131 /* if column is dead */
132 IndexType length; /* number of rows in this column */
133 union {
134 IndexType thickness; /* number of original columns represented by this */
135 /* col, if the column is alive */
136 IndexType parent; /* parent in parent tree super-column structure, if */
137 /* the column is dead */
138 } shared1;
139 union {
140 IndexType score; /* the score used to maintain heap, if col is alive */
141 IndexType order; /* pivot ordering of this column, if col is dead */
142 } shared2;
143 union {
144 IndexType headhash; /* head of a hash bucket, if col is at the head of */
145 /* a degree list */
146 IndexType hash; /* hash value, if col is not in a degree list */
147 IndexType prev; /* previous column in degree list, if col is in a */
148 /* degree list (but not at the head of a degree list) */
149 } shared3;
150 union {
151 IndexType degree_next; /* next column, if col is in a degree list */
152 IndexType hash_next; /* next column, if col is in a hash list */
153 } shared4;
154
155 inline bool is_dead() const { return start < Alive; }
156
157 inline bool is_alive() const { return start >= Alive; }
158
159 inline bool is_dead_principal() const { return start == DeadPrincipal; }
160
161 inline void kill_principal() { start = DeadPrincipal; }
162
163 inline void kill_non_principal() { start = DeadNonPrincipal; }
164};
165
166template <typename IndexType>
167struct RowStructure {
168 IndexType start; /* index for A of first col in this row */
169 IndexType length; /* number of principal columns in this row */
170 union {
171 IndexType degree; /* number of principal & non-principal columns in row */
172 IndexType p; /* used as a row pointer in init_rows_cols () */
173 } shared1;
174 union {
175 IndexType mark; /* for computing set differences and marking dead rows*/
176 IndexType first_column; /* first column in row (used in garbage collection) */
177 } shared2;
178
179 inline bool is_dead() const { return shared2.mark < Alive; }
180
181 inline bool is_alive() const { return shared2.mark >= Alive; }
182
183 inline void kill() { shared2.mark = Dead; }
184};
185
186/* ========================================================================== */
187/* === Colamd recommended memory size ======================================= */
188/* ========================================================================== */
189
190/*
191 The recommended length Alen of the array A passed to colamd is given by
192 the COLAMD_RECOMMENDED (nnz, n_row, n_col) macro. It returns -1 if any
193 argument is negative. 2*nnz space is required for the row and column
194 indices of the matrix. colamd_c (n_col) + colamd_r (n_row) space is
195 required for the Col and Row arrays, respectively, which are internal to
196 colamd. An additional n_col space is the minimal amount of "elbow room",
197 and nnz/5 more space is recommended for run time efficiency.
198
199 This macro is not needed when using symamd.
200
201 Explicit typecast to IndexType added Sept. 23, 2002, COLAMD version 2.2, to avoid
202 gcc -pedantic warning messages.
203*/
204template <typename IndexType>
205inline IndexType colamd_c(IndexType n_col) {
206 return IndexType(((n_col) + 1) * sizeof(ColStructure<IndexType>) / sizeof(IndexType));
207}
208
209template <typename IndexType>
210inline IndexType colamd_r(IndexType n_row) {
211 return IndexType(((n_row) + 1) * sizeof(RowStructure<IndexType>) / sizeof(IndexType));
212}
213
214// Prototypes of non-user callable routines
215template <typename IndexType>
216static IndexType init_rows_cols(IndexType n_row, IndexType n_col, RowStructure<IndexType> Row[],
217 ColStructure<IndexType> col[], IndexType A[], IndexType p[], IndexType stats[NStats]);
218
219template <typename IndexType>
220static void init_scoring(IndexType n_row, IndexType n_col, RowStructure<IndexType> Row[], ColStructure<IndexType> Col[],
221 IndexType A[], IndexType head[], double knobs[NKnobs], IndexType *p_n_row2,
222 IndexType *p_n_col2, IndexType *p_max_deg);
223
224template <typename IndexType>
225static IndexType find_ordering(IndexType n_row, IndexType n_col, IndexType Alen, RowStructure<IndexType> Row[],
226 ColStructure<IndexType> Col[], IndexType A[], IndexType head[], IndexType n_col2,
227 IndexType max_deg, IndexType pfree);
228
229template <typename IndexType>
230static void order_children(IndexType n_col, ColStructure<IndexType> Col[], IndexType p[]);
231
232template <typename IndexType>
233static void detect_super_cols(ColStructure<IndexType> Col[], IndexType A[], IndexType head[], IndexType row_start,
234 IndexType row_length);
235
236template <typename IndexType>
237static IndexType garbage_collection(IndexType n_row, IndexType n_col, RowStructure<IndexType> Row[],
238 ColStructure<IndexType> Col[], IndexType A[], IndexType *pfree);
239
240template <typename IndexType>
241static inline IndexType clear_mark(IndexType n_row, RowStructure<IndexType> Row[]);
242
243/* === No debugging ========================================================= */
244
245#define COLAMD_DEBUG0(params) ;
246#define COLAMD_DEBUG1(params) ;
247#define COLAMD_DEBUG2(params) ;
248#define COLAMD_DEBUG3(params) ;
249#define COLAMD_DEBUG4(params) ;
250
251#define COLAMD_ASSERT(expression) ((void)0)
252
267template <typename IndexType>
268inline IndexType recommended(IndexType nnz, IndexType n_row, IndexType n_col) {
269 if ((nnz) < 0 || (n_row) < 0 || (n_col) < 0)
270 return (-1);
271 else
272 return (2 * (nnz) + colamd_c(n_col) + colamd_r(n_row) + (n_col) + ((nnz) / 5));
273}
274
295
296static inline void set_defaults(double knobs[NKnobs]) {
297 /* === Local variables ================================================== */
298
299 int i;
300
301 if (!knobs) {
302 return; /* no knobs to initialize */
303 }
304 for (i = 0; i < NKnobs; i++) {
305 knobs[i] = 0;
306 }
307 knobs[Colamd::DenseRow] = 0.5; /* ignore rows over 50% dense */
308 knobs[Colamd::DenseCol] = 0.5; /* ignore columns over 50% dense */
309}
310
328template <typename IndexType>
329static bool compute_ordering(IndexType n_row, IndexType n_col, IndexType Alen, IndexType *A, IndexType *p,
330 double knobs[NKnobs], IndexType stats[NStats]) {
331 /* === Local variables ================================================== */
332
333 IndexType i; /* loop index */
334 IndexType nnz; /* nonzeros in A */
335 IndexType Row_size; /* size of Row [], in integers */
336 IndexType Col_size; /* size of Col [], in integers */
337 IndexType need; /* minimum required length of A */
338 Colamd::RowStructure<IndexType> *Row; /* pointer into A of Row [0..n_row] array */
339 Colamd::ColStructure<IndexType> *Col; /* pointer into A of Col [0..n_col] array */
340 IndexType n_col2; /* number of non-dense, non-empty columns */
341 IndexType n_row2; /* number of non-dense, non-empty rows */
342 IndexType ngarbage; /* number of garbage collections performed */
343 IndexType max_deg; /* maximum row degree */
344 double default_knobs[NKnobs]; /* default knobs array */
345
346 /* === Check the input arguments ======================================== */
347
348 if (!stats) {
349 COLAMD_DEBUG0(("colamd: stats not present\n"));
350 return (false);
351 }
352 for (i = 0; i < NStats; i++) {
353 stats[i] = 0;
354 }
355 stats[Colamd::Status] = Colamd::Ok;
356 stats[Colamd::Info1] = -1;
357 stats[Colamd::Info2] = -1;
358
359 if (!A) /* A is not present */
360 {
361 stats[Colamd::Status] = Colamd::ErrorANotPresent;
362 COLAMD_DEBUG0(("colamd: A not present\n"));
363 return (false);
364 }
365
366 if (!p) /* p is not present */
367 {
368 stats[Colamd::Status] = Colamd::ErrorPNotPresent;
369 COLAMD_DEBUG0(("colamd: p not present\n"));
370 return (false);
371 }
372
373 if (n_row < 0) /* n_row must be >= 0 */
374 {
375 stats[Colamd::Status] = Colamd::ErrorNrowNegative;
376 stats[Colamd::Info1] = n_row;
377 COLAMD_DEBUG0(("colamd: nrow negative %d\n", n_row));
378 return (false);
379 }
380
381 if (n_col < 0) /* n_col must be >= 0 */
382 {
383 stats[Colamd::Status] = Colamd::ErrorNcolNegative;
384 stats[Colamd::Info1] = n_col;
385 COLAMD_DEBUG0(("colamd: ncol negative %d\n", n_col));
386 return (false);
387 }
388
389 nnz = p[n_col];
390 if (nnz < 0) /* nnz must be >= 0 */
391 {
392 stats[Colamd::Status] = Colamd::ErrorNnzNegative;
393 stats[Colamd::Info1] = nnz;
394 COLAMD_DEBUG0(("colamd: number of entries negative %d\n", nnz));
395 return (false);
396 }
397
398 if (p[0] != 0) {
399 stats[Colamd::Status] = Colamd::ErrorP0Nonzero;
400 stats[Colamd::Info1] = p[0];
401 COLAMD_DEBUG0(("colamd: p[0] not zero %d\n", p[0]));
402 return (false);
403 }
404
405 /* === If no knobs, set default knobs =================================== */
406
407 if (!knobs) {
408 set_defaults(default_knobs);
409 knobs = default_knobs;
410 }
411
412 /* === Allocate the Row and Col arrays from array A ===================== */
413
414 Col_size = colamd_c(n_col);
415 Row_size = colamd_r(n_row);
416 need = 2 * nnz + n_col + Col_size + Row_size;
417
418 if (need > Alen) {
419 /* not enough space in array A to perform the ordering */
420 stats[Colamd::Status] = Colamd::ErrorATooSmall;
421 stats[Colamd::Info1] = need;
422 stats[Colamd::Info2] = Alen;
423 COLAMD_DEBUG0(("colamd: Need Alen >= %d, given only Alen = %d\n", need, Alen));
424 return (false);
425 }
426
427 Alen -= Col_size + Row_size;
428 Col = (ColStructure<IndexType> *)&A[Alen];
429 Row = (RowStructure<IndexType> *)&A[Alen + Col_size];
430
431 /* === Construct the row and column data structures ===================== */
432
433 if (!Colamd::init_rows_cols(n_row, n_col, Row, Col, A, p, stats)) {
434 /* input matrix is invalid */
435 COLAMD_DEBUG0(("colamd: Matrix invalid\n"));
436 return (false);
437 }
438
439 /* === Initialize scores, kill dense rows/columns ======================= */
440
441 Colamd::init_scoring(n_row, n_col, Row, Col, A, p, knobs, &n_row2, &n_col2, &max_deg);
442
443 /* === Order the supercolumns =========================================== */
444
445 ngarbage = Colamd::find_ordering(n_row, n_col, Alen, Row, Col, A, p, n_col2, max_deg, 2 * nnz);
446
447 /* === Order the non-principal columns ================================== */
448
449 Colamd::order_children(n_col, Col, p);
450
451 /* === Return statistics in stats ======================================= */
452
453 stats[Colamd::DenseRow] = n_row - n_row2;
454 stats[Colamd::DenseCol] = n_col - n_col2;
455 stats[Colamd::DefragCount] = ngarbage;
456 COLAMD_DEBUG0(("colamd: done.\n"));
457 return (true);
458}
459
460/* ========================================================================== */
461/* === NON-USER-CALLABLE ROUTINES: ========================================== */
462/* ========================================================================== */
463
464/* There are no user-callable routines beyond this point in the file */
465
466/* ========================================================================== */
467/* === init_rows_cols ======================================================= */
468/* ========================================================================== */
469
470/*
471 Takes the column form of the matrix in A and creates the row form of the
472 matrix. Also, row and column attributes are stored in the Col and Row
473 structs. If the columns are un-sorted or contain duplicate row indices,
474 this routine will also sort and remove duplicate row indices from the
475 column form of the matrix. Returns false if the matrix is invalid,
476 true otherwise. Not user-callable.
477*/
478template <typename IndexType>
479static IndexType init_rows_cols /* returns true if OK, or false otherwise */
480 (
481 /* === Parameters ======================================================= */
482
483 IndexType n_row, /* number of rows of A */
484 IndexType n_col, /* number of columns of A */
485 RowStructure<IndexType> Row[], /* of size n_row+1 */
486 ColStructure<IndexType> Col[], /* of size n_col+1 */
487 IndexType A[], /* row indices of A, of size Alen */
488 IndexType p[], /* pointers to columns in A, of size n_col+1 */
489 IndexType stats[NStats] /* colamd statistics */
490 ) {
491 /* === Local variables ================================================== */
492
493 IndexType col; /* a column index */
494 IndexType row; /* a row index */
495 IndexType *cp; /* a column pointer */
496 IndexType *cp_end; /* a pointer to the end of a column */
497 IndexType *rp; /* a row pointer */
498 IndexType *rp_end; /* a pointer to the end of a row */
499 IndexType last_row; /* previous row */
500
501 /* === Initialize columns, and check column pointers ==================== */
502
503 for (col = 0; col < n_col; col++) {
504 Col[col].start = p[col];
505 Col[col].length = p[col + 1] - p[col];
506
507 if ((Col[col].length) < 0) // extra parentheses to work-around gcc bug 10200
508 {
509 /* column pointers must be non-decreasing */
510 stats[Colamd::Status] = Colamd::ErrorColLengthNegative;
511 stats[Colamd::Info1] = col;
512 stats[Colamd::Info2] = Col[col].length;
513 COLAMD_DEBUG0(("colamd: col %d length %d < 0\n", col, Col[col].length));
514 return (false);
515 }
516
517 Col[col].shared1.thickness = 1;
518 Col[col].shared2.score = 0;
519 Col[col].shared3.prev = Empty;
520 Col[col].shared4.degree_next = Empty;
521 }
522
523 /* p [0..n_col] no longer needed, used as "head" in subsequent routines */
524
525 /* === Scan columns, compute row degrees, and check row indices ========= */
526
527 stats[Info3] = 0; /* number of duplicate or unsorted row indices*/
528
529 for (row = 0; row < n_row; row++) {
530 Row[row].length = 0;
531 Row[row].shared2.mark = -1;
532 }
533
534 for (col = 0; col < n_col; col++) {
535 last_row = -1;
536
537 cp = &A[p[col]];
538 cp_end = &A[p[col + 1]];
539
540 while (cp < cp_end) {
541 row = *cp++;
542
543 /* make sure row indices within range */
544 if (row < 0 || row >= n_row) {
545 stats[Colamd::Status] = Colamd::ErrorRowIndexOutOfBounds;
546 stats[Colamd::Info1] = col;
547 stats[Colamd::Info2] = row;
548 stats[Colamd::Info3] = n_row;
549 COLAMD_DEBUG0(("colamd: row %d col %d out of bounds\n", row, col));
550 return (false);
551 }
552
553 if (row <= last_row || Row[row].shared2.mark == col) {
554 /* row index are unsorted or repeated (or both), thus col */
555 /* is jumbled. This is a notice, not an error condition. */
556 stats[Colamd::Status] = Colamd::OkButJumbled;
557 stats[Colamd::Info1] = col;
558 stats[Colamd::Info2] = row;
559 (stats[Colamd::Info3])++;
560 COLAMD_DEBUG1(("colamd: row %d col %d unsorted/duplicate\n", row, col));
561 }
562
563 if (Row[row].shared2.mark != col) {
564 Row[row].length++;
565 } else {
566 /* this is a repeated entry in the column, */
567 /* it will be removed */
568 Col[col].length--;
569 }
570
571 /* mark the row as having been seen in this column */
572 Row[row].shared2.mark = col;
573
574 last_row = row;
575 }
576 }
577
578 /* === Compute row pointers ============================================= */
579
580 /* row form of the matrix starts directly after the column */
581 /* form of matrix in A */
582 Row[0].start = p[n_col];
583 Row[0].shared1.p = Row[0].start;
584 Row[0].shared2.mark = -1;
585 for (row = 1; row < n_row; row++) {
586 Row[row].start = Row[row - 1].start + Row[row - 1].length;
587 Row[row].shared1.p = Row[row].start;
588 Row[row].shared2.mark = -1;
589 }
590
591 /* === Create row form ================================================== */
592
593 if (stats[Status] == OkButJumbled) {
594 /* if cols jumbled, watch for repeated row indices */
595 for (col = 0; col < n_col; col++) {
596 cp = &A[p[col]];
597 cp_end = &A[p[col + 1]];
598 while (cp < cp_end) {
599 row = *cp++;
600 if (Row[row].shared2.mark != col) {
601 A[(Row[row].shared1.p)++] = col;
602 Row[row].shared2.mark = col;
603 }
604 }
605 }
606 } else {
607 /* if cols not jumbled, we don't need the mark (this is faster) */
608 for (col = 0; col < n_col; col++) {
609 cp = &A[p[col]];
610 cp_end = &A[p[col + 1]];
611 while (cp < cp_end) {
612 A[(Row[*cp++].shared1.p)++] = col;
613 }
614 }
615 }
616
617 /* === Clear the row marks and set row degrees ========================== */
618
619 for (row = 0; row < n_row; row++) {
620 Row[row].shared2.mark = 0;
621 Row[row].shared1.degree = Row[row].length;
622 }
623
624 /* === See if we need to re-create columns ============================== */
625
626 if (stats[Status] == OkButJumbled) {
627 COLAMD_DEBUG0(("colamd: reconstructing column form, matrix jumbled\n"));
628
629 /* === Compute col pointers ========================================= */
630
631 /* col form of the matrix starts at A [0]. */
632 /* Note, we may have a gap between the col form and the row */
633 /* form if there were duplicate entries, if so, it will be */
634 /* removed upon the first garbage collection */
635 Col[0].start = 0;
636 p[0] = Col[0].start;
637 for (col = 1; col < n_col; col++) {
638 /* note that the lengths here are for pruned columns, i.e. */
639 /* no duplicate row indices will exist for these columns */
640 Col[col].start = Col[col - 1].start + Col[col - 1].length;
641 p[col] = Col[col].start;
642 }
643
644 /* === Re-create col form =========================================== */
645
646 for (row = 0; row < n_row; row++) {
647 rp = &A[Row[row].start];
648 rp_end = rp + Row[row].length;
649 while (rp < rp_end) {
650 A[(p[*rp++])++] = row;
651 }
652 }
653 }
654
655 /* === Done. Matrix is not (or no longer) jumbled ====================== */
656
657 return (true);
658}
659
660/* ========================================================================== */
661/* === init_scoring ========================================================= */
662/* ========================================================================== */
663
664/*
665 Kills dense or empty columns and rows, calculates an initial score for
666 each column, and places all columns in the degree lists. Not user-callable.
667*/
668template <typename IndexType>
669static void init_scoring(
670 /* === Parameters ======================================================= */
671
672 IndexType n_row, /* number of rows of A */
673 IndexType n_col, /* number of columns of A */
674 RowStructure<IndexType> Row[], /* of size n_row+1 */
675 ColStructure<IndexType> Col[], /* of size n_col+1 */
676 IndexType A[], /* column form and row form of A */
677 IndexType head[], /* of size n_col+1 */
678 double knobs[NKnobs], /* parameters */
679 IndexType *p_n_row2, /* number of non-dense, non-empty rows */
680 IndexType *p_n_col2, /* number of non-dense, non-empty columns */
681 IndexType *p_max_deg /* maximum row degree */
682) {
683 /* === Local variables ================================================== */
684
685 IndexType c; /* a column index */
686 IndexType r, row; /* a row index */
687 IndexType *cp; /* a column pointer */
688 IndexType deg; /* degree of a row or column */
689 IndexType *cp_end; /* a pointer to the end of a column */
690 IndexType *new_cp; /* new column pointer */
691 IndexType col_length; /* length of pruned column */
692 IndexType score; /* current column score */
693 IndexType n_col2; /* number of non-dense, non-empty columns */
694 IndexType n_row2; /* number of non-dense, non-empty rows */
695 IndexType dense_row_count; /* remove rows with more entries than this */
696 IndexType dense_col_count; /* remove cols with more entries than this */
697 IndexType min_score; /* smallest column score */
698 IndexType max_deg; /* maximum row degree */
699 IndexType next_col; /* Used to add to degree list.*/
700
701 /* === Extract knobs ==================================================== */
702
703 dense_row_count = numext::maxi(IndexType(0), numext::mini(IndexType(knobs[Colamd::DenseRow] * n_col), n_col));
704 dense_col_count = numext::maxi(IndexType(0), numext::mini(IndexType(knobs[Colamd::DenseCol] * n_row), n_row));
705 COLAMD_DEBUG1(("colamd: densecount: %d %d\n", dense_row_count, dense_col_count));
706 max_deg = 0;
707 n_col2 = n_col;
708 n_row2 = n_row;
709
710 /* === Kill empty columns =============================================== */
711
712 /* Put the empty columns at the end in their natural order, so that LU */
713 /* factorization can proceed as far as possible. */
714 for (c = n_col - 1; c >= 0; c--) {
715 deg = Col[c].length;
716 if (deg == 0) {
717 /* this is an empty column, kill and order it last */
718 Col[c].shared2.order = --n_col2;
719 Col[c].kill_principal();
720 }
721 }
722 COLAMD_DEBUG1(("colamd: null columns killed: %d\n", n_col - n_col2));
723
724 /* === Kill dense columns =============================================== */
725
726 /* Put the dense columns at the end, in their natural order */
727 for (c = n_col - 1; c >= 0; c--) {
728 /* skip any dead columns */
729 if (Col[c].is_dead()) {
730 continue;
731 }
732 deg = Col[c].length;
733 if (deg > dense_col_count) {
734 /* this is a dense column, kill and order it last */
735 Col[c].shared2.order = --n_col2;
736 /* decrement the row degrees */
737 cp = &A[Col[c].start];
738 cp_end = cp + Col[c].length;
739 while (cp < cp_end) {
740 Row[*cp++].shared1.degree--;
741 }
742 Col[c].kill_principal();
743 }
744 }
745 COLAMD_DEBUG1(("colamd: Dense and null columns killed: %d\n", n_col - n_col2));
746
747 /* === Kill dense and empty rows ======================================== */
748
749 for (r = 0; r < n_row; r++) {
750 deg = Row[r].shared1.degree;
751 COLAMD_ASSERT(deg >= 0 && deg <= n_col);
752 if (deg > dense_row_count || deg == 0) {
753 /* kill a dense or empty row */
754 Row[r].kill();
755 --n_row2;
756 } else {
757 /* keep track of max degree of remaining rows */
758 max_deg = numext::maxi(max_deg, deg);
759 }
760 }
761 COLAMD_DEBUG1(("colamd: Dense and null rows killed: %d\n", n_row - n_row2));
762
763 /* === Compute initial column scores ==================================== */
764
765 /* At this point the row degrees are accurate. They reflect the number */
766 /* of "live" (non-dense) columns in each row. No empty rows exist. */
767 /* Some "live" columns may contain only dead rows, however. These are */
768 /* pruned in the code below. */
769
770 /* now find the initial matlab score for each column */
771 for (c = n_col - 1; c >= 0; c--) {
772 /* skip dead column */
773 if (Col[c].is_dead()) {
774 continue;
775 }
776 score = 0;
777 cp = &A[Col[c].start];
778 new_cp = cp;
779 cp_end = cp + Col[c].length;
780 while (cp < cp_end) {
781 /* get a row */
782 row = *cp++;
783 /* skip if dead */
784 if (Row[row].is_dead()) {
785 continue;
786 }
787 /* compact the column */
788 *new_cp++ = row;
789 /* add row's external degree */
790 score += Row[row].shared1.degree - 1;
791 /* guard against integer overflow */
792 score = numext::mini(score, n_col);
793 }
794 /* determine pruned column length */
795 col_length = (IndexType)(new_cp - &A[Col[c].start]);
796 if (col_length == 0) {
797 /* a newly-made null column (all rows in this col are "dense" */
798 /* and have already been killed) */
799 COLAMD_DEBUG2(("Newly null killed: %d\n", c));
800 Col[c].shared2.order = --n_col2;
801 Col[c].kill_principal();
802 } else {
803 /* set column length and set score */
804 COLAMD_ASSERT(score >= 0);
805 COLAMD_ASSERT(score <= n_col);
806 Col[c].length = col_length;
807 Col[c].shared2.score = score;
808 }
809 }
810 COLAMD_DEBUG1(("colamd: Dense, null, and newly-null columns killed: %d\n", n_col - n_col2));
811
812 /* At this point, all empty rows and columns are dead. All live columns */
813 /* are "clean" (containing no dead rows) and simplicial (no supercolumns */
814 /* yet). Rows may contain dead columns, but all live rows contain at */
815 /* least one live column. */
816
817 /* === Initialize degree lists ========================================== */
818
819 /* clear the hash buckets */
820 for (c = 0; c <= n_col; c++) {
821 head[c] = Empty;
822 }
823 min_score = n_col;
824 /* place in reverse order, so low column indices are at the front */
825 /* of the lists. This is to encourage natural tie-breaking */
826 for (c = n_col - 1; c >= 0; c--) {
827 /* only add principal columns to degree lists */
828 if (Col[c].is_alive()) {
829 COLAMD_DEBUG4(("place %d score %d minscore %d ncol %d\n", c, Col[c].shared2.score, min_score, n_col));
830
831 /* === Add columns score to DList =============================== */
832
833 score = Col[c].shared2.score;
834
835 COLAMD_ASSERT(min_score >= 0);
836 COLAMD_ASSERT(min_score <= n_col);
837 COLAMD_ASSERT(score >= 0);
838 COLAMD_ASSERT(score <= n_col);
839 COLAMD_ASSERT(head[score] >= Empty);
840
841 /* now add this column to dList at proper score location */
842 next_col = head[score];
843 Col[c].shared3.prev = Empty;
844 Col[c].shared4.degree_next = next_col;
845
846 /* if there already was a column with the same score, set its */
847 /* previous pointer to this new column */
848 if (next_col != Empty) {
849 Col[next_col].shared3.prev = c;
850 }
851 head[score] = c;
852
853 /* see if this score is less than current min */
854 min_score = numext::mini(min_score, score);
855 }
856 }
857
858 /* === Return number of remaining columns, and max row degree =========== */
859
860 *p_n_col2 = n_col2;
861 *p_n_row2 = n_row2;
862 *p_max_deg = max_deg;
863}
864
865/* ========================================================================== */
866/* === find_ordering ======================================================== */
867/* ========================================================================== */
868
869/*
870 Order the principal columns of the supercolumn form of the matrix
871 (no supercolumns on input). Uses a minimum approximate column minimum
872 degree ordering method. Not user-callable.
873*/
874template <typename IndexType>
875static IndexType find_ordering /* return the number of garbage collections */
876 (
877 /* === Parameters ======================================================= */
878
879 IndexType n_row, /* number of rows of A */
880 IndexType n_col, /* number of columns of A */
881 IndexType Alen, /* size of A, 2*nnz + n_col or larger */
882 RowStructure<IndexType> Row[], /* of size n_row+1 */
883 ColStructure<IndexType> Col[], /* of size n_col+1 */
884 IndexType A[], /* column form and row form of A */
885 IndexType head[], /* of size n_col+1 */
886 IndexType n_col2, /* Remaining columns to order */
887 IndexType max_deg, /* Maximum row degree */
888 IndexType pfree /* index of first free slot (2*nnz on entry) */
889 ) {
890 /* === Local variables ================================================== */
891
892 IndexType k; /* current pivot ordering step */
893 IndexType pivot_col; /* current pivot column */
894 IndexType *cp; /* a column pointer */
895 IndexType *rp; /* a row pointer */
896 IndexType pivot_row; /* current pivot row */
897 IndexType *new_cp; /* modified column pointer */
898 IndexType *new_rp; /* modified row pointer */
899 IndexType pivot_row_start; /* pointer to start of pivot row */
900 IndexType pivot_row_degree; /* number of columns in pivot row */
901 IndexType pivot_row_length; /* number of supercolumns in pivot row */
902 IndexType pivot_col_score; /* score of pivot column */
903 IndexType needed_memory; /* free space needed for pivot row */
904 IndexType *cp_end; /* pointer to the end of a column */
905 IndexType *rp_end; /* pointer to the end of a row */
906 IndexType row; /* a row index */
907 IndexType col; /* a column index */
908 IndexType max_score; /* maximum possible score */
909 IndexType cur_score; /* score of current column */
910 unsigned int hash; /* hash value for supernode detection */
911 IndexType head_column; /* head of hash bucket */
912 IndexType first_col; /* first column in hash bucket */
913 IndexType tag_mark; /* marker value for mark array */
914 IndexType row_mark; /* Row [row].shared2.mark */
915 IndexType set_difference; /* set difference size of row with pivot row */
916 IndexType min_score; /* smallest column score */
917 IndexType col_thickness; /* "thickness" (no. of columns in a supercol) */
918 IndexType max_mark; /* maximum value of tag_mark */
919 IndexType pivot_col_thickness; /* number of columns represented by pivot col */
920 IndexType prev_col; /* Used by Dlist operations. */
921 IndexType next_col; /* Used by Dlist operations. */
922 IndexType ngarbage; /* number of garbage collections performed */
923
924 /* === Initialization and clear mark ==================================== */
925
926 max_mark = INT_MAX - n_col; /* INT_MAX defined in <limits.h> */
927 tag_mark = Colamd::clear_mark(n_row, Row);
928 min_score = 0;
929 ngarbage = 0;
930 COLAMD_DEBUG1(("colamd: Ordering, n_col2=%d\n", n_col2));
931
932 /* === Order the columns ================================================ */
933
934 for (k = 0; k < n_col2; /* 'k' is incremented below */) {
935 /* === Select pivot column, and order it ============================ */
936
937 /* make sure degree list isn't empty */
938 COLAMD_ASSERT(min_score >= 0);
939 COLAMD_ASSERT(min_score <= n_col);
940 COLAMD_ASSERT(head[min_score] >= Empty);
941
942 /* get pivot column from head of minimum degree list */
943 while (min_score < n_col && head[min_score] == Empty) {
944 min_score++;
945 }
946 pivot_col = head[min_score];
947 COLAMD_ASSERT(pivot_col >= 0 && pivot_col <= n_col);
948 next_col = Col[pivot_col].shared4.degree_next;
949 head[min_score] = next_col;
950 if (next_col != Empty) {
951 Col[next_col].shared3.prev = Empty;
952 }
953
954 COLAMD_ASSERT(Col[pivot_col].is_alive());
955 COLAMD_DEBUG3(("Pivot col: %d\n", pivot_col));
956
957 /* remember score for defrag check */
958 pivot_col_score = Col[pivot_col].shared2.score;
959
960 /* the pivot column is the kth column in the pivot order */
961 Col[pivot_col].shared2.order = k;
962
963 /* increment order count by column thickness */
964 pivot_col_thickness = Col[pivot_col].shared1.thickness;
965 k += pivot_col_thickness;
966 COLAMD_ASSERT(pivot_col_thickness > 0);
967
968 /* === Garbage_collection, if necessary ============================= */
969
970 needed_memory = numext::mini(pivot_col_score, n_col - k);
971 if (pfree + needed_memory >= Alen) {
972 pfree = Colamd::garbage_collection(n_row, n_col, Row, Col, A, &A[pfree]);
973 ngarbage++;
974 /* after garbage collection we will have enough */
975 COLAMD_ASSERT(pfree + needed_memory < Alen);
976 /* garbage collection has wiped out the Row[].shared2.mark array */
977 tag_mark = Colamd::clear_mark(n_row, Row);
978 }
979
980 /* === Compute pivot row pattern ==================================== */
981
982 /* get starting location for this new merged row */
983 pivot_row_start = pfree;
984
985 /* initialize new row counts to zero */
986 pivot_row_degree = 0;
987
988 /* tag pivot column as having been visited so it isn't included */
989 /* in merged pivot row */
990 Col[pivot_col].shared1.thickness = -pivot_col_thickness;
991
992 /* pivot row is the union of all rows in the pivot column pattern */
993 cp = &A[Col[pivot_col].start];
994 cp_end = cp + Col[pivot_col].length;
995 while (cp < cp_end) {
996 /* get a row */
997 row = *cp++;
998 COLAMD_DEBUG4(("Pivot col pattern %d %d\n", Row[row].is_alive(), row));
999 /* skip if row is dead */
1000 if (Row[row].is_dead()) {
1001 continue;
1002 }
1003 rp = &A[Row[row].start];
1004 rp_end = rp + Row[row].length;
1005 while (rp < rp_end) {
1006 /* get a column */
1007 col = *rp++;
1008 /* add the column, if alive and untagged */
1009 col_thickness = Col[col].shared1.thickness;
1010 if (col_thickness > 0 && Col[col].is_alive()) {
1011 /* tag column in pivot row */
1012 Col[col].shared1.thickness = -col_thickness;
1013 COLAMD_ASSERT(pfree < Alen);
1014 /* place column in pivot row */
1015 A[pfree++] = col;
1016 pivot_row_degree += col_thickness;
1017 }
1018 }
1019 }
1020
1021 /* clear tag on pivot column */
1022 Col[pivot_col].shared1.thickness = pivot_col_thickness;
1023 max_deg = numext::maxi(max_deg, pivot_row_degree);
1024
1025 /* === Kill all rows used to construct pivot row ==================== */
1026
1027 /* also kill pivot row, temporarily */
1028 cp = &A[Col[pivot_col].start];
1029 cp_end = cp + Col[pivot_col].length;
1030 while (cp < cp_end) {
1031 /* may be killing an already dead row */
1032 row = *cp++;
1033 COLAMD_DEBUG3(("Kill row in pivot col: %d\n", row));
1034 Row[row].kill();
1035 }
1036
1037 /* === Select a row index to use as the new pivot row =============== */
1038
1039 pivot_row_length = pfree - pivot_row_start;
1040 if (pivot_row_length > 0) {
1041 /* pick the "pivot" row arbitrarily (first row in col) */
1042 pivot_row = A[Col[pivot_col].start];
1043 COLAMD_DEBUG3(("Pivotal row is %d\n", pivot_row));
1044 } else {
1045 /* there is no pivot row, since it is of zero length */
1046 pivot_row = Empty;
1047 COLAMD_ASSERT(pivot_row_length == 0);
1048 }
1049 COLAMD_ASSERT(Col[pivot_col].length > 0 || pivot_row_length == 0);
1050
1051 /* === Approximate degree computation =============================== */
1052
1053 /* Here begins the computation of the approximate degree. The column */
1054 /* score is the sum of the pivot row "length", plus the size of the */
1055 /* set differences of each row in the column minus the pattern of the */
1056 /* pivot row itself. The column ("thickness") itself is also */
1057 /* excluded from the column score (we thus use an approximate */
1058 /* external degree). */
1059
1060 /* The time taken by the following code (compute set differences, and */
1061 /* add them up) is proportional to the size of the data structure */
1062 /* being scanned - that is, the sum of the sizes of each column in */
1063 /* the pivot row. Thus, the amortized time to compute a column score */
1064 /* is proportional to the size of that column (where size, in this */
1065 /* context, is the column "length", or the number of row indices */
1066 /* in that column). The number of row indices in a column is */
1067 /* monotonically non-decreasing, from the length of the original */
1068 /* column on input to colamd. */
1069
1070 /* === Compute set differences ====================================== */
1071
1072 COLAMD_DEBUG3(("** Computing set differences phase. **\n"));
1073
1074 /* pivot row is currently dead - it will be revived later. */
1075
1076 COLAMD_DEBUG3(("Pivot row: "));
1077 /* for each column in pivot row */
1078 rp = &A[pivot_row_start];
1079 rp_end = rp + pivot_row_length;
1080 while (rp < rp_end) {
1081 col = *rp++;
1082 COLAMD_ASSERT(Col[col].is_alive() && col != pivot_col);
1083 COLAMD_DEBUG3(("Col: %d\n", col));
1084
1085 /* clear tags used to construct pivot row pattern */
1086 col_thickness = -Col[col].shared1.thickness;
1087 COLAMD_ASSERT(col_thickness > 0);
1088 Col[col].shared1.thickness = col_thickness;
1089
1090 /* === Remove column from degree list =========================== */
1091
1092 cur_score = Col[col].shared2.score;
1093 prev_col = Col[col].shared3.prev;
1094 next_col = Col[col].shared4.degree_next;
1095 COLAMD_ASSERT(cur_score >= 0);
1096 COLAMD_ASSERT(cur_score <= n_col);
1097 COLAMD_ASSERT(cur_score >= Empty);
1098 if (prev_col == Empty) {
1099 head[cur_score] = next_col;
1100 } else {
1101 Col[prev_col].shared4.degree_next = next_col;
1102 }
1103 if (next_col != Empty) {
1104 Col[next_col].shared3.prev = prev_col;
1105 }
1106
1107 /* === Scan the column ========================================== */
1108
1109 cp = &A[Col[col].start];
1110 cp_end = cp + Col[col].length;
1111 while (cp < cp_end) {
1112 /* get a row */
1113 row = *cp++;
1114 /* skip if dead */
1115 if (Row[row].is_dead()) {
1116 continue;
1117 }
1118 row_mark = Row[row].shared2.mark;
1119 COLAMD_ASSERT(row != pivot_row);
1120 set_difference = row_mark - tag_mark;
1121 /* check if the row has been seen yet */
1122 if (set_difference < 0) {
1123 COLAMD_ASSERT(Row[row].shared1.degree <= max_deg);
1124 set_difference = Row[row].shared1.degree;
1125 }
1126 /* subtract column thickness from this row's set difference */
1127 set_difference -= col_thickness;
1128 COLAMD_ASSERT(set_difference >= 0);
1129 /* absorb this row if the set difference becomes zero */
1130 if (set_difference == 0) {
1131 COLAMD_DEBUG3(("aggressive absorption. Row: %d\n", row));
1132 Row[row].kill();
1133 } else {
1134 /* save the new mark */
1135 Row[row].shared2.mark = set_difference + tag_mark;
1136 }
1137 }
1138 }
1139
1140 /* === Add up set differences for each column ======================= */
1141
1142 COLAMD_DEBUG3(("** Adding set differences phase. **\n"));
1143
1144 /* for each column in pivot row */
1145 rp = &A[pivot_row_start];
1146 rp_end = rp + pivot_row_length;
1147 while (rp < rp_end) {
1148 /* get a column */
1149 col = *rp++;
1150 COLAMD_ASSERT(Col[col].is_alive() && col != pivot_col);
1151 hash = 0;
1152 cur_score = 0;
1153 cp = &A[Col[col].start];
1154 /* compact the column */
1155 new_cp = cp;
1156 cp_end = cp + Col[col].length;
1157
1158 COLAMD_DEBUG4(("Adding set diffs for Col: %d.\n", col));
1159
1160 while (cp < cp_end) {
1161 /* get a row */
1162 row = *cp++;
1163 COLAMD_ASSERT(row >= 0 && row < n_row);
1164 /* skip if dead */
1165 if (Row[row].is_dead()) {
1166 continue;
1167 }
1168 row_mark = Row[row].shared2.mark;
1169 COLAMD_ASSERT(row_mark > tag_mark);
1170 /* compact the column */
1171 *new_cp++ = row;
1172 /* compute hash function */
1173 hash += row;
1174 /* add set difference */
1175 cur_score += row_mark - tag_mark;
1176 /* integer overflow... */
1177 cur_score = numext::mini(cur_score, n_col);
1178 }
1179
1180 /* recompute the column's length */
1181 Col[col].length = (IndexType)(new_cp - &A[Col[col].start]);
1182
1183 /* === Further mass elimination ================================= */
1184
1185 if (Col[col].length == 0) {
1186 COLAMD_DEBUG4(("further mass elimination. Col: %d\n", col));
1187 /* nothing left but the pivot row in this column */
1188 Col[col].kill_principal();
1189 pivot_row_degree -= Col[col].shared1.thickness;
1190 COLAMD_ASSERT(pivot_row_degree >= 0);
1191 /* order it */
1192 Col[col].shared2.order = k;
1193 /* increment order count by column thickness */
1194 k += Col[col].shared1.thickness;
1195 } else {
1196 /* === Prepare for supercolumn detection ==================== */
1197
1198 COLAMD_DEBUG4(("Preparing supercol detection for Col: %d.\n", col));
1199
1200 /* save score so far */
1201 Col[col].shared2.score = cur_score;
1202
1203 /* add column to hash table, for supercolumn detection */
1204 hash %= n_col + 1;
1205
1206 COLAMD_DEBUG4((" Hash = %d, n_col = %d.\n", hash, n_col));
1207 COLAMD_ASSERT(hash <= n_col);
1208
1209 head_column = head[hash];
1210 if (head_column > Empty) {
1211 /* degree list "hash" is non-empty, use prev (shared3) of */
1212 /* first column in degree list as head of hash bucket */
1213 first_col = Col[head_column].shared3.headhash;
1214 Col[head_column].shared3.headhash = col;
1215 } else {
1216 /* degree list "hash" is empty, use head as hash bucket */
1217 first_col = -(head_column + 2);
1218 head[hash] = -(col + 2);
1219 }
1220 Col[col].shared4.hash_next = first_col;
1221
1222 /* save hash function in Col [col].shared3.hash */
1223 Col[col].shared3.hash = (IndexType)hash;
1224 COLAMD_ASSERT(Col[col].is_alive());
1225 }
1226 }
1227
1228 /* The approximate external column degree is now computed. */
1229
1230 /* === Supercolumn detection ======================================== */
1231
1232 COLAMD_DEBUG3(("** Supercolumn detection phase. **\n"));
1233
1234 Colamd::detect_super_cols(Col, A, head, pivot_row_start, pivot_row_length);
1235
1236 /* === Kill the pivotal column ====================================== */
1237
1238 Col[pivot_col].kill_principal();
1239
1240 /* === Clear mark =================================================== */
1241
1242 tag_mark += (max_deg + 1);
1243 if (tag_mark >= max_mark) {
1244 COLAMD_DEBUG2(("clearing tag_mark\n"));
1245 tag_mark = Colamd::clear_mark(n_row, Row);
1246 }
1247
1248 /* === Finalize the new pivot row, and column scores ================ */
1249
1250 COLAMD_DEBUG3(("** Finalize scores phase. **\n"));
1251
1252 /* for each column in pivot row */
1253 rp = &A[pivot_row_start];
1254 /* compact the pivot row */
1255 new_rp = rp;
1256 rp_end = rp + pivot_row_length;
1257 while (rp < rp_end) {
1258 col = *rp++;
1259 /* skip dead columns */
1260 if (Col[col].is_dead()) {
1261 continue;
1262 }
1263 *new_rp++ = col;
1264 /* add new pivot row to column */
1265 A[Col[col].start + (Col[col].length++)] = pivot_row;
1266
1267 /* retrieve score so far and add on pivot row's degree. */
1268 /* (we wait until here for this in case the pivot */
1269 /* row's degree was reduced due to mass elimination). */
1270 cur_score = Col[col].shared2.score + pivot_row_degree;
1271
1272 /* calculate the max possible score as the number of */
1273 /* external columns minus the 'k' value minus the */
1274 /* columns thickness */
1275 max_score = n_col - k - Col[col].shared1.thickness;
1276
1277 /* make the score the external degree of the union-of-rows */
1278 cur_score -= Col[col].shared1.thickness;
1279
1280 /* make sure score is less or equal than the max score */
1281 cur_score = numext::mini(cur_score, max_score);
1282 COLAMD_ASSERT(cur_score >= 0);
1283
1284 /* store updated score */
1285 Col[col].shared2.score = cur_score;
1286
1287 /* === Place column back in degree list ========================= */
1288
1289 COLAMD_ASSERT(min_score >= 0);
1290 COLAMD_ASSERT(min_score <= n_col);
1291 COLAMD_ASSERT(cur_score >= 0);
1292 COLAMD_ASSERT(cur_score <= n_col);
1293 COLAMD_ASSERT(head[cur_score] >= Empty);
1294 next_col = head[cur_score];
1295 Col[col].shared4.degree_next = next_col;
1296 Col[col].shared3.prev = Empty;
1297 if (next_col != Empty) {
1298 Col[next_col].shared3.prev = col;
1299 }
1300 head[cur_score] = col;
1301
1302 /* see if this score is less than current min */
1303 min_score = numext::mini(min_score, cur_score);
1304 }
1305
1306 /* === Resurrect the new pivot row ================================== */
1307
1308 if (pivot_row_degree > 0) {
1309 /* update pivot row length to reflect any cols that were killed */
1310 /* during super-col detection and mass elimination */
1311 Row[pivot_row].start = pivot_row_start;
1312 Row[pivot_row].length = (IndexType)(new_rp - &A[pivot_row_start]);
1313 Row[pivot_row].shared1.degree = pivot_row_degree;
1314 Row[pivot_row].shared2.mark = 0;
1315 /* pivot row is no longer dead */
1316 }
1317 }
1318
1319 /* === All principal columns have now been ordered ====================== */
1320
1321 return (ngarbage);
1322}
1323
1324/* ========================================================================== */
1325/* === order_children ======================================================= */
1326/* ========================================================================== */
1327
1328/*
1329 The find_ordering routine has ordered all of the principal columns (the
1330 representatives of the supercolumns). The non-principal columns have not
1331 yet been ordered. This routine orders those columns by walking up the
1332 parent tree (a column is a child of the column which absorbed it). The
1333 final permutation vector is then placed in p [0 ... n_col-1], with p [0]
1334 being the first column, and p [n_col-1] being the last. It doesn't look
1335 like it at first glance, but be assured that this routine takes time linear
1336 in the number of columns. Although not immediately obvious, the time
1337 taken by this routine is O (n_col), that is, linear in the number of
1338 columns. Not user-callable.
1339*/
1340template <typename IndexType>
1341static inline void order_children(
1342 /* === Parameters ======================================================= */
1343
1344 IndexType n_col, /* number of columns of A */
1345 ColStructure<IndexType> Col[], /* of size n_col+1 */
1346 IndexType p[] /* p [0 ... n_col-1] is the column permutation*/
1347) {
1348 /* === Local variables ================================================== */
1349
1350 IndexType i; /* loop counter for all columns */
1351 IndexType c; /* column index */
1352 IndexType parent; /* index of column's parent */
1353 IndexType order; /* column's order */
1354
1355 /* === Order each non-principal column ================================== */
1356
1357 for (i = 0; i < n_col; i++) {
1358 /* find an un-ordered non-principal column */
1359 COLAMD_ASSERT(col_is_dead(Col, i));
1360 if (!Col[i].is_dead_principal() && Col[i].shared2.order == Empty) {
1361 parent = i;
1362 /* once found, find its principal parent */
1363 do {
1364 parent = Col[parent].shared1.parent;
1365 } while (!Col[parent].is_dead_principal());
1366
1367 /* now, order all un-ordered non-principal columns along path */
1368 /* to this parent. collapse tree at the same time */
1369 c = i;
1370 /* get order of parent */
1371 order = Col[parent].shared2.order;
1372
1373 do {
1374 COLAMD_ASSERT(Col[c].shared2.order == Empty);
1375
1376 /* order this column */
1377 Col[c].shared2.order = order++;
1378 /* collapse tree */
1379 Col[c].shared1.parent = parent;
1380
1381 /* get immediate parent of this column */
1382 c = Col[c].shared1.parent;
1383
1384 /* continue until we hit an ordered column. There are */
1385 /* guaranteed not to be anymore unordered columns */
1386 /* above an ordered column */
1387 } while (Col[c].shared2.order == Empty);
1388
1389 /* re-order the super_col parent to largest order for this group */
1390 Col[parent].shared2.order = order;
1391 }
1392 }
1393
1394 /* === Generate the permutation ========================================= */
1395
1396 for (c = 0; c < n_col; c++) {
1397 p[Col[c].shared2.order] = c;
1398 }
1399}
1400
1401/* ========================================================================== */
1402/* === detect_super_cols ==================================================== */
1403/* ========================================================================== */
1404
1405/*
1406 Detects supercolumns by finding matches between columns in the hash buckets.
1407 Check amongst columns in the set A [row_start ... row_start + row_length-1].
1408 The columns under consideration are currently *not* in the degree lists,
1409 and have already been placed in the hash buckets.
1410
1411 The hash bucket for columns whose hash function is equal to h is stored
1412 as follows:
1413
1414 if head [h] is >= 0, then head [h] contains a degree list, so:
1415
1416 head [h] is the first column in degree bucket h.
1417 Col [head [h]].headhash gives the first column in hash bucket h.
1418
1419 otherwise, the degree list is empty, and:
1420
1421 -(head [h] + 2) is the first column in hash bucket h.
1422
1423 For a column c in a hash bucket, Col [c].shared3.prev is NOT a "previous
1424 column" pointer. Col [c].shared3.hash is used instead as the hash number
1425 for that column. The value of Col [c].shared4.hash_next is the next column
1426 in the same hash bucket.
1427
1428 Assuming no, or "few" hash collisions, the time taken by this routine is
1429 linear in the sum of the sizes (lengths) of each column whose score has
1430 just been computed in the approximate degree computation.
1431 Not user-callable.
1432*/
1433template <typename IndexType>
1434static void detect_super_cols(
1435 /* === Parameters ======================================================= */
1436
1437 ColStructure<IndexType> Col[], /* of size n_col+1 */
1438 IndexType A[], /* row indices of A */
1439 IndexType head[], /* head of degree lists and hash buckets */
1440 IndexType row_start, /* pointer to set of columns to check */
1441 IndexType row_length /* number of columns to check */
1442) {
1443 /* === Local variables ================================================== */
1444
1445 IndexType hash; /* hash value for a column */
1446 IndexType *rp; /* pointer to a row */
1447 IndexType c; /* a column index */
1448 IndexType super_c; /* column index of the column to absorb into */
1449 IndexType *cp1; /* column pointer for column super_c */
1450 IndexType *cp2; /* column pointer for column c */
1451 IndexType length; /* length of column super_c */
1452 IndexType prev_c; /* column preceding c in hash bucket */
1453 IndexType i; /* loop counter */
1454 IndexType *rp_end; /* pointer to the end of the row */
1455 IndexType col; /* a column index in the row to check */
1456 IndexType head_column; /* first column in hash bucket or degree list */
1457 IndexType first_col; /* first column in hash bucket */
1458
1459 /* === Consider each column in the row ================================== */
1460
1461 rp = &A[row_start];
1462 rp_end = rp + row_length;
1463 while (rp < rp_end) {
1464 col = *rp++;
1465 if (Col[col].is_dead()) {
1466 continue;
1467 }
1468
1469 /* get hash number for this column */
1470 hash = Col[col].shared3.hash;
1471 COLAMD_ASSERT(hash <= n_col);
1472
1473 /* === Get the first column in this hash bucket ===================== */
1474
1475 head_column = head[hash];
1476 if (head_column > Empty) {
1477 first_col = Col[head_column].shared3.headhash;
1478 } else {
1479 first_col = -(head_column + 2);
1480 }
1481
1482 /* === Consider each column in the hash bucket ====================== */
1483
1484 for (super_c = first_col; super_c != Empty; super_c = Col[super_c].shared4.hash_next) {
1485 COLAMD_ASSERT(Col[super_c].is_alive());
1486 COLAMD_ASSERT(Col[super_c].shared3.hash == hash);
1487 length = Col[super_c].length;
1488
1489 /* prev_c is the column preceding column c in the hash bucket */
1490 prev_c = super_c;
1491
1492 /* === Compare super_c with all columns after it ================ */
1493
1494 for (c = Col[super_c].shared4.hash_next; c != Empty; c = Col[c].shared4.hash_next) {
1495 COLAMD_ASSERT(c != super_c);
1496 COLAMD_ASSERT(Col[c].is_alive());
1497 COLAMD_ASSERT(Col[c].shared3.hash == hash);
1498
1499 /* not identical if lengths or scores are different */
1500 if (Col[c].length != length || Col[c].shared2.score != Col[super_c].shared2.score) {
1501 prev_c = c;
1502 continue;
1503 }
1504
1505 /* compare the two columns */
1506 cp1 = &A[Col[super_c].start];
1507 cp2 = &A[Col[c].start];
1508
1509 for (i = 0; i < length; i++) {
1510 /* the columns are "clean" (no dead rows) */
1511 COLAMD_ASSERT(cp1->is_alive());
1512 COLAMD_ASSERT(cp2->is_alive());
1513 /* row indices will same order for both supercols, */
1514 /* no gather scatter necessary */
1515 if (*cp1++ != *cp2++) {
1516 break;
1517 }
1518 }
1519
1520 /* the two columns are different if the for-loop "broke" */
1521 if (i != length) {
1522 prev_c = c;
1523 continue;
1524 }
1525
1526 /* === Got it! two columns are identical =================== */
1527
1528 COLAMD_ASSERT(Col[c].shared2.score == Col[super_c].shared2.score);
1529
1530 Col[super_c].shared1.thickness += Col[c].shared1.thickness;
1531 Col[c].shared1.parent = super_c;
1532 Col[c].kill_non_principal();
1533 /* order c later, in order_children() */
1534 Col[c].shared2.order = Empty;
1535 /* remove c from hash bucket */
1536 Col[prev_c].shared4.hash_next = Col[c].shared4.hash_next;
1537 }
1538 }
1539
1540 /* === Empty this hash bucket ======================================= */
1541
1542 if (head_column > Empty) {
1543 /* corresponding degree list "hash" is not empty */
1544 Col[head_column].shared3.headhash = Empty;
1545 } else {
1546 /* corresponding degree list "hash" is empty */
1547 head[hash] = Empty;
1548 }
1549 }
1550}
1551
1552/* ========================================================================== */
1553/* === garbage_collection =================================================== */
1554/* ========================================================================== */
1555
1556/*
1557 Defragments and compacts columns and rows in the workspace A. Used when
1558 all available memory has been used while performing row merging. Returns
1559 the index of the first free position in A, after garbage collection. The
1560 time taken by this routine is linear is the size of the array A, which is
1561 itself linear in the number of nonzeros in the input matrix.
1562 Not user-callable.
1563*/
1564template <typename IndexType>
1565static IndexType garbage_collection /* returns the new value of pfree */
1566 (
1567 /* === Parameters ======================================================= */
1568
1569 IndexType n_row, /* number of rows */
1570 IndexType n_col, /* number of columns */
1571 RowStructure<IndexType> Row[], /* row info */
1572 ColStructure<IndexType> Col[], /* column info */
1573 IndexType A[], /* A [0 ... Alen-1] holds the matrix */
1574 IndexType *pfree /* &A [0] ... pfree is in use */
1575 ) {
1576 /* === Local variables ================================================== */
1577
1578 IndexType *psrc; /* source pointer */
1579 IndexType *pdest; /* destination pointer */
1580 IndexType j; /* counter */
1581 IndexType r; /* a row index */
1582 IndexType c; /* a column index */
1583 IndexType length; /* length of a row or column */
1584
1585 /* === Defragment the columns =========================================== */
1586
1587 pdest = &A[0];
1588 for (c = 0; c < n_col; c++) {
1589 if (Col[c].is_alive()) {
1590 psrc = &A[Col[c].start];
1591
1592 /* move and compact the column */
1593 COLAMD_ASSERT(pdest <= psrc);
1594 Col[c].start = (IndexType)(pdest - &A[0]);
1595 length = Col[c].length;
1596 for (j = 0; j < length; j++) {
1597 r = *psrc++;
1598 if (Row[r].is_alive()) {
1599 *pdest++ = r;
1600 }
1601 }
1602 Col[c].length = (IndexType)(pdest - &A[Col[c].start]);
1603 }
1604 }
1605
1606 /* === Prepare to defragment the rows =================================== */
1607
1608 for (r = 0; r < n_row; r++) {
1609 if (Row[r].is_alive()) {
1610 if (Row[r].length == 0) {
1611 /* this row is of zero length. cannot compact it, so kill it */
1612 COLAMD_DEBUG3(("Defrag row kill\n"));
1613 Row[r].kill();
1614 } else {
1615 /* save first column index in Row [r].shared2.first_column */
1616 psrc = &A[Row[r].start];
1617 Row[r].shared2.first_column = *psrc;
1618 COLAMD_ASSERT(Row[r].is_alive());
1619 /* flag the start of the row with the one's complement of row */
1620 *psrc = ones_complement(r);
1621 }
1622 }
1623 }
1624
1625 /* === Defragment the rows ============================================== */
1626
1627 psrc = pdest;
1628 while (psrc < pfree) {
1629 /* find a negative number ... the start of a row */
1630 if (*psrc++ < 0) {
1631 psrc--;
1632 /* get the row index */
1633 r = ones_complement(*psrc);
1634 COLAMD_ASSERT(r >= 0 && r < n_row);
1635 /* restore first column index */
1636 *psrc = Row[r].shared2.first_column;
1637 COLAMD_ASSERT(Row[r].is_alive());
1638
1639 /* move and compact the row */
1640 COLAMD_ASSERT(pdest <= psrc);
1641 Row[r].start = (IndexType)(pdest - &A[0]);
1642 length = Row[r].length;
1643 for (j = 0; j < length; j++) {
1644 c = *psrc++;
1645 if (Col[c].is_alive()) {
1646 *pdest++ = c;
1647 }
1648 }
1649 Row[r].length = (IndexType)(pdest - &A[Row[r].start]);
1650 }
1651 }
1652 /* ensure we found all the rows */
1653 COLAMD_ASSERT(debug_rows == 0);
1654
1655 /* === Return the new value of pfree ==================================== */
1656
1657 return ((IndexType)(pdest - &A[0]));
1658}
1659
1660/* ========================================================================== */
1661/* === clear_mark =========================================================== */
1662/* ========================================================================== */
1663
1664/*
1665 Clears the Row [].shared2.mark array, and returns the new tag_mark.
1666 Return value is the new tag_mark. Not user-callable.
1667*/
1668template <typename IndexType>
1669static inline IndexType clear_mark /* return the new value for tag_mark */
1670 (
1671 /* === Parameters ======================================================= */
1672
1673 IndexType n_row, /* number of rows in A */
1674 RowStructure<IndexType> Row[] /* Row [0 ... n_row-1].shared2.mark is set to zero */
1675 ) {
1676 /* === Local variables ================================================== */
1677
1678 IndexType r;
1679
1680 for (r = 0; r < n_row; r++) {
1681 if (Row[r].is_alive()) {
1682 Row[r].shared2.mark = 0;
1683 }
1684 }
1685 return (1);
1686}
1687
1688} // namespace Colamd
1689} // namespace internal
1690} // namespace Eigen
1691#endif