blob: 7ca3f33b12f61258819d7bd2bf8fe3222397aace [file] [log] [blame]
Brian Silverman72890c22015-09-19 14:37:37 -04001// This file is part of Eigen, a lightweight C++ template library
2// for linear algebra.
3//
4// Copyright (C) 2010 Gael Guennebaud <gael.guennebaud@inria.fr>
Austin Schuhc55b0172022-02-20 17:52:35 -08005//
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/.
Brian Silverman72890c22015-09-19 14:37:37 -04009
10/*
Brian Silverman72890c22015-09-19 14:37:37 -040011NOTE: this routine has been adapted from the CSparse library:
12
13Copyright (c) 2006, Timothy A. Davis.
Austin Schuh189376f2018-12-20 22:11:15 +110014http://www.suitesparse.com
Brian Silverman72890c22015-09-19 14:37:37 -040015
Austin Schuhc55b0172022-02-20 17:52:35 -080016The author of CSparse, Timothy A. Davis., has executed a license with Google LLC
17to permit distribution of this code and derivative works as part of Eigen under
18the Mozilla Public License v. 2.0, as stated at the top of this file.
Brian Silverman72890c22015-09-19 14:37:37 -040019*/
20
Brian Silverman72890c22015-09-19 14:37:37 -040021#ifndef EIGEN_SPARSE_AMD_H
22#define EIGEN_SPARSE_AMD_H
23
24namespace Eigen {
25
26namespace internal {
27
28template<typename T> inline T amd_flip(const T& i) { return -i-2; }
29template<typename T> inline T amd_unflip(const T& i) { return i<0 ? amd_flip(i) : i; }
30template<typename T0, typename T1> inline bool amd_marked(const T0* w, const T1& j) { return w[j]<0; }
31template<typename T0, typename T1> inline void amd_mark(const T0* w, const T1& j) { return w[j] = amd_flip(w[j]); }
32
33/* clear w */
Austin Schuh189376f2018-12-20 22:11:15 +110034template<typename StorageIndex>
35static StorageIndex cs_wclear (StorageIndex mark, StorageIndex lemax, StorageIndex *w, StorageIndex n)
Brian Silverman72890c22015-09-19 14:37:37 -040036{
Austin Schuh189376f2018-12-20 22:11:15 +110037 StorageIndex k;
Brian Silverman72890c22015-09-19 14:37:37 -040038 if(mark < 2 || (mark + lemax < 0))
39 {
40 for(k = 0; k < n; k++)
41 if(w[k] != 0)
42 w[k] = 1;
43 mark = 2;
44 }
45 return (mark); /* at this point, w[0..n-1] < mark holds */
46}
47
48/* depth-first search and postorder of a tree rooted at node j */
Austin Schuh189376f2018-12-20 22:11:15 +110049template<typename StorageIndex>
50StorageIndex cs_tdfs(StorageIndex j, StorageIndex k, StorageIndex *head, const StorageIndex *next, StorageIndex *post, StorageIndex *stack)
Brian Silverman72890c22015-09-19 14:37:37 -040051{
Austin Schuh189376f2018-12-20 22:11:15 +110052 StorageIndex i, p, top = 0;
Brian Silverman72890c22015-09-19 14:37:37 -040053 if(!head || !next || !post || !stack) return (-1); /* check inputs */
54 stack[0] = j; /* place j on the stack */
55 while (top >= 0) /* while (stack is not empty) */
56 {
57 p = stack[top]; /* p = top of stack */
58 i = head[p]; /* i = youngest child of p */
59 if(i == -1)
60 {
61 top--; /* p has no unordered children left */
62 post[k++] = p; /* node p is the kth postordered node */
63 }
64 else
65 {
66 head[p] = next[i]; /* remove i from children of p */
67 stack[++top] = i; /* start dfs on child node i */
68 }
69 }
70 return k;
71}
72
73
74/** \internal
75 * \ingroup OrderingMethods_Module
76 * Approximate minimum degree ordering algorithm.
Austin Schuh189376f2018-12-20 22:11:15 +110077 *
78 * \param[in] C the input selfadjoint matrix stored in compressed column major format.
79 * \param[out] perm the permutation P reducing the fill-in of the input matrix \a C
80 *
81 * Note that the input matrix \a C must be complete, that is both the upper and lower parts have to be stored, as well as the diagonal entries.
Brian Silverman72890c22015-09-19 14:37:37 -040082 * On exit the values of C are destroyed */
Austin Schuh189376f2018-12-20 22:11:15 +110083template<typename Scalar, typename StorageIndex>
84void minimum_degree_ordering(SparseMatrix<Scalar,ColMajor,StorageIndex>& C, PermutationMatrix<Dynamic,Dynamic,StorageIndex>& perm)
Brian Silverman72890c22015-09-19 14:37:37 -040085{
86 using std::sqrt;
87
Austin Schuh189376f2018-12-20 22:11:15 +110088 StorageIndex d, dk, dext, lemax = 0, e, elenk, eln, i, j, k, k1,
89 k2, k3, jlast, ln, dense, nzmax, mindeg = 0, nvi, nvj, nvk, mark, wnvi,
90 ok, nel = 0, p, p1, p2, p3, p4, pj, pk, pk1, pk2, pn, q, t, h;
Brian Silverman72890c22015-09-19 14:37:37 -040091
Austin Schuh189376f2018-12-20 22:11:15 +110092 StorageIndex n = StorageIndex(C.cols());
93 dense = std::max<StorageIndex> (16, StorageIndex(10 * sqrt(double(n)))); /* find dense threshold */
94 dense = (std::min)(n-2, dense);
Brian Silverman72890c22015-09-19 14:37:37 -040095
Austin Schuh189376f2018-12-20 22:11:15 +110096 StorageIndex cnz = StorageIndex(C.nonZeros());
Brian Silverman72890c22015-09-19 14:37:37 -040097 perm.resize(n+1);
98 t = cnz + cnz/5 + 2*n; /* add elbow room to C */
99 C.resizeNonZeros(t);
100
Austin Schuh189376f2018-12-20 22:11:15 +1100101 // get workspace
102 ei_declare_aligned_stack_constructed_variable(StorageIndex,W,8*(n+1),0);
103 StorageIndex* len = W;
104 StorageIndex* nv = W + (n+1);
105 StorageIndex* next = W + 2*(n+1);
106 StorageIndex* head = W + 3*(n+1);
107 StorageIndex* elen = W + 4*(n+1);
108 StorageIndex* degree = W + 5*(n+1);
109 StorageIndex* w = W + 6*(n+1);
110 StorageIndex* hhead = W + 7*(n+1);
111 StorageIndex* last = perm.indices().data(); /* use P as workspace for last */
Brian Silverman72890c22015-09-19 14:37:37 -0400112
113 /* --- Initialize quotient graph ---------------------------------------- */
Austin Schuh189376f2018-12-20 22:11:15 +1100114 StorageIndex* Cp = C.outerIndexPtr();
115 StorageIndex* Ci = C.innerIndexPtr();
Brian Silverman72890c22015-09-19 14:37:37 -0400116 for(k = 0; k < n; k++)
117 len[k] = Cp[k+1] - Cp[k];
118 len[n] = 0;
119 nzmax = t;
120
121 for(i = 0; i <= n; i++)
122 {
123 head[i] = -1; // degree list i is empty
124 last[i] = -1;
125 next[i] = -1;
126 hhead[i] = -1; // hash list i is empty
127 nv[i] = 1; // node i is just one node
128 w[i] = 1; // node i is alive
129 elen[i] = 0; // Ek of node i is empty
130 degree[i] = len[i]; // degree of node i
131 }
Austin Schuh189376f2018-12-20 22:11:15 +1100132 mark = internal::cs_wclear<StorageIndex>(0, 0, w, n); /* clear w */
Brian Silverman72890c22015-09-19 14:37:37 -0400133
134 /* --- Initialize degree lists ------------------------------------------ */
135 for(i = 0; i < n; i++)
136 {
137 bool has_diag = false;
138 for(p = Cp[i]; p<Cp[i+1]; ++p)
139 if(Ci[p]==i)
140 {
141 has_diag = true;
142 break;
143 }
144
145 d = degree[i];
Austin Schuh189376f2018-12-20 22:11:15 +1100146 if(d == 1 && has_diag) /* node i is empty */
Brian Silverman72890c22015-09-19 14:37:37 -0400147 {
148 elen[i] = -2; /* element i is dead */
149 nel++;
150 Cp[i] = -1; /* i is a root of assembly tree */
151 w[i] = 0;
152 }
153 else if(d > dense || !has_diag) /* node i is dense or has no structural diagonal element */
154 {
155 nv[i] = 0; /* absorb i into element n */
156 elen[i] = -1; /* node i is dead */
157 nel++;
158 Cp[i] = amd_flip (n);
159 nv[n]++;
160 }
161 else
162 {
163 if(head[d] != -1) last[head[d]] = i;
164 next[i] = head[d]; /* put node i in degree list d */
165 head[d] = i;
166 }
167 }
168
169 elen[n] = -2; /* n is a dead element */
170 Cp[n] = -1; /* n is a root of assembly tree */
171 w[n] = 0; /* n is a dead element */
172
173 while (nel < n) /* while (selecting pivots) do */
174 {
175 /* --- Select node of minimum approximate degree -------------------- */
176 for(k = -1; mindeg < n && (k = head[mindeg]) == -1; mindeg++) {}
177 if(next[k] != -1) last[next[k]] = -1;
178 head[mindeg] = next[k]; /* remove k from degree list */
179 elenk = elen[k]; /* elenk = |Ek| */
180 nvk = nv[k]; /* # of nodes k represents */
181 nel += nvk; /* nv[k] nodes of A eliminated */
182
183 /* --- Garbage collection ------------------------------------------- */
184 if(elenk > 0 && cnz + mindeg >= nzmax)
185 {
186 for(j = 0; j < n; j++)
187 {
188 if((p = Cp[j]) >= 0) /* j is a live node or element */
189 {
190 Cp[j] = Ci[p]; /* save first entry of object */
191 Ci[p] = amd_flip (j); /* first entry is now amd_flip(j) */
192 }
193 }
194 for(q = 0, p = 0; p < cnz; ) /* scan all of memory */
195 {
196 if((j = amd_flip (Ci[p++])) >= 0) /* found object j */
197 {
198 Ci[q] = Cp[j]; /* restore first entry of object */
199 Cp[j] = q++; /* new pointer to object j */
200 for(k3 = 0; k3 < len[j]-1; k3++) Ci[q++] = Ci[p++];
201 }
202 }
203 cnz = q; /* Ci[cnz...nzmax-1] now free */
204 }
205
206 /* --- Construct new element ---------------------------------------- */
207 dk = 0;
208 nv[k] = -nvk; /* flag k as in Lk */
209 p = Cp[k];
210 pk1 = (elenk == 0) ? p : cnz; /* do in place if elen[k] == 0 */
211 pk2 = pk1;
212 for(k1 = 1; k1 <= elenk + 1; k1++)
213 {
214 if(k1 > elenk)
215 {
216 e = k; /* search the nodes in k */
217 pj = p; /* list of nodes starts at Ci[pj]*/
218 ln = len[k] - elenk; /* length of list of nodes in k */
219 }
220 else
221 {
222 e = Ci[p++]; /* search the nodes in e */
223 pj = Cp[e];
224 ln = len[e]; /* length of list of nodes in e */
225 }
226 for(k2 = 1; k2 <= ln; k2++)
227 {
228 i = Ci[pj++];
229 if((nvi = nv[i]) <= 0) continue; /* node i dead, or seen */
230 dk += nvi; /* degree[Lk] += size of node i */
231 nv[i] = -nvi; /* negate nv[i] to denote i in Lk*/
232 Ci[pk2++] = i; /* place i in Lk */
233 if(next[i] != -1) last[next[i]] = last[i];
234 if(last[i] != -1) /* remove i from degree list */
235 {
236 next[last[i]] = next[i];
237 }
238 else
239 {
240 head[degree[i]] = next[i];
241 }
242 }
243 if(e != k)
244 {
245 Cp[e] = amd_flip (k); /* absorb e into k */
246 w[e] = 0; /* e is now a dead element */
247 }
248 }
249 if(elenk != 0) cnz = pk2; /* Ci[cnz...nzmax] is free */
250 degree[k] = dk; /* external degree of k - |Lk\i| */
251 Cp[k] = pk1; /* element k is in Ci[pk1..pk2-1] */
252 len[k] = pk2 - pk1;
253 elen[k] = -2; /* k is now an element */
254
255 /* --- Find set differences ----------------------------------------- */
Austin Schuh189376f2018-12-20 22:11:15 +1100256 mark = internal::cs_wclear<StorageIndex>(mark, lemax, w, n); /* clear w if necessary */
Brian Silverman72890c22015-09-19 14:37:37 -0400257 for(pk = pk1; pk < pk2; pk++) /* scan 1: find |Le\Lk| */
258 {
259 i = Ci[pk];
260 if((eln = elen[i]) <= 0) continue;/* skip if elen[i] empty */
261 nvi = -nv[i]; /* nv[i] was negated */
262 wnvi = mark - nvi;
263 for(p = Cp[i]; p <= Cp[i] + eln - 1; p++) /* scan Ei */
264 {
265 e = Ci[p];
266 if(w[e] >= mark)
267 {
268 w[e] -= nvi; /* decrement |Le\Lk| */
269 }
270 else if(w[e] != 0) /* ensure e is a live element */
271 {
272 w[e] = degree[e] + wnvi; /* 1st time e seen in scan 1 */
273 }
274 }
275 }
276
277 /* --- Degree update ------------------------------------------------ */
278 for(pk = pk1; pk < pk2; pk++) /* scan2: degree update */
279 {
280 i = Ci[pk]; /* consider node i in Lk */
281 p1 = Cp[i];
282 p2 = p1 + elen[i] - 1;
283 pn = p1;
284 for(h = 0, d = 0, p = p1; p <= p2; p++) /* scan Ei */
285 {
286 e = Ci[p];
287 if(w[e] != 0) /* e is an unabsorbed element */
288 {
289 dext = w[e] - mark; /* dext = |Le\Lk| */
290 if(dext > 0)
291 {
292 d += dext; /* sum up the set differences */
293 Ci[pn++] = e; /* keep e in Ei */
294 h += e; /* compute the hash of node i */
295 }
296 else
297 {
298 Cp[e] = amd_flip (k); /* aggressive absorb. e->k */
299 w[e] = 0; /* e is a dead element */
300 }
301 }
302 }
303 elen[i] = pn - p1 + 1; /* elen[i] = |Ei| */
304 p3 = pn;
305 p4 = p1 + len[i];
306 for(p = p2 + 1; p < p4; p++) /* prune edges in Ai */
307 {
308 j = Ci[p];
309 if((nvj = nv[j]) <= 0) continue; /* node j dead or in Lk */
310 d += nvj; /* degree(i) += |j| */
311 Ci[pn++] = j; /* place j in node list of i */
312 h += j; /* compute hash for node i */
313 }
314 if(d == 0) /* check for mass elimination */
315 {
316 Cp[i] = amd_flip (k); /* absorb i into k */
317 nvi = -nv[i];
318 dk -= nvi; /* |Lk| -= |i| */
319 nvk += nvi; /* |k| += nv[i] */
320 nel += nvi;
321 nv[i] = 0;
322 elen[i] = -1; /* node i is dead */
323 }
324 else
325 {
Austin Schuh189376f2018-12-20 22:11:15 +1100326 degree[i] = std::min<StorageIndex> (degree[i], d); /* update degree(i) */
Brian Silverman72890c22015-09-19 14:37:37 -0400327 Ci[pn] = Ci[p3]; /* move first node to end */
328 Ci[p3] = Ci[p1]; /* move 1st el. to end of Ei */
329 Ci[p1] = k; /* add k as 1st element in of Ei */
330 len[i] = pn - p1 + 1; /* new len of adj. list of node i */
331 h %= n; /* finalize hash of i */
332 next[i] = hhead[h]; /* place i in hash bucket */
333 hhead[h] = i;
Austin Schuh189376f2018-12-20 22:11:15 +1100334 last[i] = h; /* save hash of i in last[i] */
Brian Silverman72890c22015-09-19 14:37:37 -0400335 }
336 } /* scan2 is done */
337 degree[k] = dk; /* finalize |Lk| */
Austin Schuh189376f2018-12-20 22:11:15 +1100338 lemax = std::max<StorageIndex>(lemax, dk);
339 mark = internal::cs_wclear<StorageIndex>(mark+lemax, lemax, w, n); /* clear w */
Brian Silverman72890c22015-09-19 14:37:37 -0400340
341 /* --- Supernode detection ------------------------------------------ */
342 for(pk = pk1; pk < pk2; pk++)
343 {
344 i = Ci[pk];
345 if(nv[i] >= 0) continue; /* skip if i is dead */
346 h = last[i]; /* scan hash bucket of node i */
347 i = hhead[h];
348 hhead[h] = -1; /* hash bucket will be empty */
349 for(; i != -1 && next[i] != -1; i = next[i], mark++)
350 {
351 ln = len[i];
352 eln = elen[i];
353 for(p = Cp[i]+1; p <= Cp[i] + ln-1; p++) w[Ci[p]] = mark;
354 jlast = i;
355 for(j = next[i]; j != -1; ) /* compare i with all j */
356 {
357 ok = (len[j] == ln) && (elen[j] == eln);
358 for(p = Cp[j] + 1; ok && p <= Cp[j] + ln - 1; p++)
359 {
360 if(w[Ci[p]] != mark) ok = 0; /* compare i and j*/
361 }
362 if(ok) /* i and j are identical */
363 {
364 Cp[j] = amd_flip (i); /* absorb j into i */
365 nv[i] += nv[j];
366 nv[j] = 0;
367 elen[j] = -1; /* node j is dead */
368 j = next[j]; /* delete j from hash bucket */
369 next[jlast] = j;
370 }
371 else
372 {
373 jlast = j; /* j and i are different */
374 j = next[j];
375 }
376 }
377 }
378 }
379
380 /* --- Finalize new element------------------------------------------ */
381 for(p = pk1, pk = pk1; pk < pk2; pk++) /* finalize Lk */
382 {
383 i = Ci[pk];
384 if((nvi = -nv[i]) <= 0) continue;/* skip if i is dead */
385 nv[i] = nvi; /* restore nv[i] */
386 d = degree[i] + dk - nvi; /* compute external degree(i) */
Austin Schuh189376f2018-12-20 22:11:15 +1100387 d = std::min<StorageIndex> (d, n - nel - nvi);
Brian Silverman72890c22015-09-19 14:37:37 -0400388 if(head[d] != -1) last[head[d]] = i;
389 next[i] = head[d]; /* put i back in degree list */
390 last[i] = -1;
391 head[d] = i;
Austin Schuh189376f2018-12-20 22:11:15 +1100392 mindeg = std::min<StorageIndex> (mindeg, d); /* find new minimum degree */
Brian Silverman72890c22015-09-19 14:37:37 -0400393 degree[i] = d;
394 Ci[p++] = i; /* place i in Lk */
395 }
396 nv[k] = nvk; /* # nodes absorbed into k */
397 if((len[k] = p-pk1) == 0) /* length of adj list of element k*/
398 {
399 Cp[k] = -1; /* k is a root of the tree */
400 w[k] = 0; /* k is now a dead element */
401 }
402 if(elenk != 0) cnz = p; /* free unused space in Lk */
403 }
404
405 /* --- Postordering ----------------------------------------------------- */
406 for(i = 0; i < n; i++) Cp[i] = amd_flip (Cp[i]);/* fix assembly tree */
407 for(j = 0; j <= n; j++) head[j] = -1;
408 for(j = n; j >= 0; j--) /* place unordered nodes in lists */
409 {
410 if(nv[j] > 0) continue; /* skip if j is an element */
411 next[j] = head[Cp[j]]; /* place j in list of its parent */
412 head[Cp[j]] = j;
413 }
414 for(e = n; e >= 0; e--) /* place elements in lists */
415 {
416 if(nv[e] <= 0) continue; /* skip unless e is an element */
417 if(Cp[e] != -1)
418 {
419 next[e] = head[Cp[e]]; /* place e in list of its parent */
420 head[Cp[e]] = e;
421 }
422 }
423 for(k = 0, i = 0; i <= n; i++) /* postorder the assembly tree */
424 {
Austin Schuh189376f2018-12-20 22:11:15 +1100425 if(Cp[i] == -1) k = internal::cs_tdfs<StorageIndex>(i, k, head, next, perm.indices().data(), w);
Brian Silverman72890c22015-09-19 14:37:37 -0400426 }
427
428 perm.indices().conservativeResize(n);
Brian Silverman72890c22015-09-19 14:37:37 -0400429}
430
431} // namespace internal
432
433} // end namespace Eigen
434
435#endif // EIGEN_SPARSE_AMD_H