Eigen  5.0.1
 
Loading...
Searching...
No Matches
Amd.h
1// 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>
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/*
12NOTE: this routine has been adapted from the CSparse library:
13
14Copyright (c) 2006, Timothy A. Davis.
15http://www.suitesparse.com
16
17The author of CSparse, Timothy A. Davis., has executed a license with Google LLC
18to permit distribution of this code and derivative works as part of Eigen under
19the Mozilla Public License v. 2.0, as stated at the top of this file.
20*/
21
22#ifndef EIGEN_SPARSE_AMD_H
23#define EIGEN_SPARSE_AMD_H
24
25// IWYU pragma: private
26#include "./InternalHeaderCheck.h"
27
28namespace Eigen {
29
30namespace internal {
31
32template <typename T>
33inline T amd_flip(const T& i) {
34 return -i - 2;
35}
36
37/* clear w */
38template <typename StorageIndex>
39static StorageIndex cs_wclear(StorageIndex mark, StorageIndex lemax, StorageIndex* w, StorageIndex n) {
40 StorageIndex k;
41 if (mark < 2 || (mark + lemax < 0)) {
42 for (k = 0; k < n; k++)
43 if (w[k] != 0) w[k] = 1;
44 mark = 2;
45 }
46 return (mark); /* at this point, w[0..n-1] < mark holds */
47}
48
49/* depth-first search and postorder of a tree rooted at node j */
50template <typename StorageIndex>
51StorageIndex cs_tdfs(StorageIndex j, StorageIndex k, StorageIndex* head, const StorageIndex* next, StorageIndex* post,
52 StorageIndex* stack) {
53 StorageIndex i, p, top = 0;
54 if (!head || !next || !post || !stack) return (-1); /* check inputs */
55 stack[0] = j; /* place j on the stack */
56 while (top >= 0) /* while (stack is not empty) */
57 {
58 p = stack[top]; /* p = top of stack */
59 i = head[p]; /* i = youngest child of p */
60 if (i == -1) {
61 top--; /* p has no unordered children left */
62 post[k++] = p; /* node p is the kth postordered node */
63 } else {
64 head[p] = next[i]; /* remove i from children of p */
65 stack[++top] = i; /* start dfs on child node i */
66 }
67 }
68 return k;
69}
70
80template <typename Scalar, typename StorageIndex>
81void minimum_degree_ordering(SparseMatrix<Scalar, ColMajor, StorageIndex>& C,
82 PermutationMatrix<Dynamic, Dynamic, StorageIndex>& perm) {
83 using std::sqrt;
84
85 StorageIndex d, dk, dext, lemax = 0, e, elenk, eln, i, j, k, k1, k2, k3, jlast, ln, dense, nzmax, mindeg = 0, nvi,
86 nvj, nvk, mark, wnvi, ok, nel = 0, p, p1, p2, p3, p4, pj, pk, pk1, pk2, pn, q, t, h;
87
88 StorageIndex n = StorageIndex(C.cols());
89 dense = std::max<StorageIndex>(16, StorageIndex(10 * sqrt(double(n)))); /* find dense threshold */
90 dense = (std::min)(n - 2, dense);
91
92 StorageIndex cnz = StorageIndex(C.nonZeros());
93 perm.resize(n + 1);
94 t = cnz + cnz / 5 + 2 * n; /* add elbow room to C */
95 C.resizeNonZeros(t);
96
97 // get workspace
98 ei_declare_aligned_stack_constructed_variable(StorageIndex, W, 8 * (n + 1), 0);
99 StorageIndex* len = W;
100 StorageIndex* nv = W + (n + 1);
101 StorageIndex* next = W + 2 * (n + 1);
102 StorageIndex* head = W + 3 * (n + 1);
103 StorageIndex* elen = W + 4 * (n + 1);
104 StorageIndex* degree = W + 5 * (n + 1);
105 StorageIndex* w = W + 6 * (n + 1);
106 StorageIndex* hhead = W + 7 * (n + 1);
107 StorageIndex* last = perm.indices().data(); /* use P as workspace for last */
108
109 /* --- Initialize quotient graph ---------------------------------------- */
110 StorageIndex* Cp = C.outerIndexPtr();
111 StorageIndex* Ci = C.innerIndexPtr();
112 for (k = 0; k < n; k++) len[k] = Cp[k + 1] - Cp[k];
113 len[n] = 0;
114 nzmax = t;
115
116 for (i = 0; i <= n; i++) {
117 head[i] = -1; // degree list i is empty
118 last[i] = -1;
119 next[i] = -1;
120 hhead[i] = -1; // hash list i is empty
121 nv[i] = 1; // node i is just one node
122 w[i] = 1; // node i is alive
123 elen[i] = 0; // Ek of node i is empty
124 degree[i] = len[i]; // degree of node i
125 }
126 mark = internal::cs_wclear<StorageIndex>(0, 0, w, n); /* clear w */
127
128 /* --- Initialize degree lists ------------------------------------------ */
129 for (i = 0; i < n; i++) {
130 bool has_diag = false;
131 for (p = Cp[i]; p < Cp[i + 1]; ++p)
132 if (Ci[p] == i) {
133 has_diag = true;
134 break;
135 }
136
137 d = degree[i];
138 if (d == 1 && has_diag) /* node i is empty */
139 {
140 elen[i] = -2; /* element i is dead */
141 nel++;
142 Cp[i] = -1; /* i is a root of assembly tree */
143 w[i] = 0;
144 } else if (d > dense || !has_diag) /* node i is dense or has no structural diagonal element */
145 {
146 nv[i] = 0; /* absorb i into element n */
147 elen[i] = -1; /* node i is dead */
148 nel++;
149 Cp[i] = amd_flip(n);
150 nv[n]++;
151 } else {
152 if (head[d] != -1) last[head[d]] = i;
153 next[i] = head[d]; /* put node i in degree list d */
154 head[d] = i;
155 }
156 }
157
158 elen[n] = -2; /* n is a dead element */
159 Cp[n] = -1; /* n is a root of assembly tree */
160 w[n] = 0; /* n is a dead element */
161
162 while (nel < n) /* while (selecting pivots) do */
163 {
164 /* --- Select node of minimum approximate degree -------------------- */
165 for (k = -1; mindeg < n && (k = head[mindeg]) == -1; mindeg++) {
166 }
167 if (next[k] != -1) last[next[k]] = -1;
168 head[mindeg] = next[k]; /* remove k from degree list */
169 elenk = elen[k]; /* elenk = |Ek| */
170 nvk = nv[k]; /* # of nodes k represents */
171 nel += nvk; /* nv[k] nodes of A eliminated */
172
173 /* --- Garbage collection ------------------------------------------- */
174 if (elenk > 0 && cnz + mindeg >= nzmax) {
175 for (j = 0; j < n; j++) {
176 if ((p = Cp[j]) >= 0) /* j is a live node or element */
177 {
178 Cp[j] = Ci[p]; /* save first entry of object */
179 Ci[p] = amd_flip(j); /* first entry is now amd_flip(j) */
180 }
181 }
182 for (q = 0, p = 0; p < cnz;) /* scan all of memory */
183 {
184 if ((j = amd_flip(Ci[p++])) >= 0) /* found object j */
185 {
186 Ci[q] = Cp[j]; /* restore first entry of object */
187 Cp[j] = q++; /* new pointer to object j */
188 for (k3 = 0; k3 < len[j] - 1; k3++) Ci[q++] = Ci[p++];
189 }
190 }
191 cnz = q; /* Ci[cnz...nzmax-1] now free */
192 }
193
194 /* --- Construct new element ---------------------------------------- */
195 dk = 0;
196 nv[k] = -nvk; /* flag k as in Lk */
197 p = Cp[k];
198 pk1 = (elenk == 0) ? p : cnz; /* do in place if elen[k] == 0 */
199 pk2 = pk1;
200 for (k1 = 1; k1 <= elenk + 1; k1++) {
201 if (k1 > elenk) {
202 e = k; /* search the nodes in k */
203 pj = p; /* list of nodes starts at Ci[pj]*/
204 ln = len[k] - elenk; /* length of list of nodes in k */
205 } else {
206 e = Ci[p++]; /* search the nodes in e */
207 pj = Cp[e];
208 ln = len[e]; /* length of list of nodes in e */
209 }
210 for (k2 = 1; k2 <= ln; k2++) {
211 i = Ci[pj++];
212 if ((nvi = nv[i]) <= 0) continue; /* node i dead, or seen */
213 dk += nvi; /* degree[Lk] += size of node i */
214 nv[i] = -nvi; /* negate nv[i] to denote i in Lk*/
215 Ci[pk2++] = i; /* place i in Lk */
216 if (next[i] != -1) last[next[i]] = last[i];
217 if (last[i] != -1) /* remove i from degree list */
218 {
219 next[last[i]] = next[i];
220 } else {
221 head[degree[i]] = next[i];
222 }
223 }
224 if (e != k) {
225 Cp[e] = amd_flip(k); /* absorb e into k */
226 w[e] = 0; /* e is now a dead element */
227 }
228 }
229 if (elenk != 0) cnz = pk2; /* Ci[cnz...nzmax] is free */
230 degree[k] = dk; /* external degree of k - |Lk\i| */
231 Cp[k] = pk1; /* element k is in Ci[pk1..pk2-1] */
232 len[k] = pk2 - pk1;
233 elen[k] = -2; /* k is now an element */
234
235 /* --- Find set differences ----------------------------------------- */
236 mark = internal::cs_wclear<StorageIndex>(mark, lemax, w, n); /* clear w if necessary */
237 for (pk = pk1; pk < pk2; pk++) /* scan 1: find |Le\Lk| */
238 {
239 i = Ci[pk];
240 if ((eln = elen[i]) <= 0) continue; /* skip if elen[i] empty */
241 nvi = -nv[i]; /* nv[i] was negated */
242 wnvi = mark - nvi;
243 for (p = Cp[i]; p <= Cp[i] + eln - 1; p++) /* scan Ei */
244 {
245 e = Ci[p];
246 if (w[e] >= mark) {
247 w[e] -= nvi; /* decrement |Le\Lk| */
248 } else if (w[e] != 0) /* ensure e is a live element */
249 {
250 w[e] = degree[e] + wnvi; /* 1st time e seen in scan 1 */
251 }
252 }
253 }
254
255 /* --- Degree update ------------------------------------------------ */
256 for (pk = pk1; pk < pk2; pk++) /* scan2: degree update */
257 {
258 i = Ci[pk]; /* consider node i in Lk */
259 p1 = Cp[i];
260 p2 = p1 + elen[i] - 1;
261 pn = p1;
262 for (h = 0, d = 0, p = p1; p <= p2; p++) /* scan Ei */
263 {
264 e = Ci[p];
265 if (w[e] != 0) /* e is an unabsorbed element */
266 {
267 dext = w[e] - mark; /* dext = |Le\Lk| */
268 if (dext > 0) {
269 d += dext; /* sum up the set differences */
270 Ci[pn++] = e; /* keep e in Ei */
271 h += e; /* compute the hash of node i */
272 } else {
273 Cp[e] = amd_flip(k); /* aggressive absorb. e->k */
274 w[e] = 0; /* e is a dead element */
275 }
276 }
277 }
278 elen[i] = pn - p1 + 1; /* elen[i] = |Ei| */
279 p3 = pn;
280 p4 = p1 + len[i];
281 for (p = p2 + 1; p < p4; p++) /* prune edges in Ai */
282 {
283 j = Ci[p];
284 if ((nvj = nv[j]) <= 0) continue; /* node j dead or in Lk */
285 d += nvj; /* degree(i) += |j| */
286 Ci[pn++] = j; /* place j in node list of i */
287 h += j; /* compute hash for node i */
288 }
289 if (d == 0) /* check for mass elimination */
290 {
291 Cp[i] = amd_flip(k); /* absorb i into k */
292 nvi = -nv[i];
293 dk -= nvi; /* |Lk| -= |i| */
294 nvk += nvi; /* |k| += nv[i] */
295 nel += nvi;
296 nv[i] = 0;
297 elen[i] = -1; /* node i is dead */
298 } else {
299 degree[i] = std::min<StorageIndex>(degree[i], d); /* update degree(i) */
300 Ci[pn] = Ci[p3]; /* move first node to end */
301 Ci[p3] = Ci[p1]; /* move 1st el. to end of Ei */
302 Ci[p1] = k; /* add k as 1st element in Ei */
303 len[i] = pn - p1 + 1; /* new len of adj. list of node i */
304 h %= n; /* finalize hash of i */
305 next[i] = hhead[h]; /* place i in hash bucket */
306 hhead[h] = i;
307 last[i] = h; /* save hash of i in last[i] */
308 }
309 } /* scan2 is done */
310 degree[k] = dk; /* finalize |Lk| */
311 lemax = std::max<StorageIndex>(lemax, dk);
312 mark = internal::cs_wclear<StorageIndex>(mark + lemax, lemax, w, n); /* clear w */
313
314 /* --- Supernode detection ------------------------------------------ */
315 for (pk = pk1; pk < pk2; pk++) {
316 i = Ci[pk];
317 if (nv[i] >= 0) continue; /* skip if i is dead */
318 h = last[i]; /* scan hash bucket of node i */
319 i = hhead[h];
320 hhead[h] = -1; /* hash bucket will be empty */
321 for (; i != -1 && next[i] != -1; i = next[i], mark++) {
322 ln = len[i];
323 eln = elen[i];
324 for (p = Cp[i] + 1; p <= Cp[i] + ln - 1; p++) w[Ci[p]] = mark;
325 jlast = i;
326 for (j = next[i]; j != -1;) /* compare i with all j */
327 {
328 ok = (len[j] == ln) && (elen[j] == eln);
329 for (p = Cp[j] + 1; ok && p <= Cp[j] + ln - 1; p++) {
330 if (w[Ci[p]] != mark) ok = 0; /* compare i and j*/
331 }
332 if (ok) /* i and j are identical */
333 {
334 Cp[j] = amd_flip(i); /* absorb j into i */
335 nv[i] += nv[j];
336 nv[j] = 0;
337 elen[j] = -1; /* node j is dead */
338 j = next[j]; /* delete j from hash bucket */
339 next[jlast] = j;
340 } else {
341 jlast = j; /* j and i are different */
342 j = next[j];
343 }
344 }
345 }
346 }
347
348 /* --- Finalize new element------------------------------------------ */
349 for (p = pk1, pk = pk1; pk < pk2; pk++) /* finalize Lk */
350 {
351 i = Ci[pk];
352 if ((nvi = -nv[i]) <= 0) continue; /* skip if i is dead */
353 nv[i] = nvi; /* restore nv[i] */
354 d = degree[i] + dk - nvi; /* compute external degree(i) */
355 d = std::min<StorageIndex>(d, n - nel - nvi);
356 if (head[d] != -1) last[head[d]] = i;
357 next[i] = head[d]; /* put i back in degree list */
358 last[i] = -1;
359 head[d] = i;
360 mindeg = std::min<StorageIndex>(mindeg, d); /* find new minimum degree */
361 degree[i] = d;
362 Ci[p++] = i; /* place i in Lk */
363 }
364 nv[k] = nvk; /* # nodes absorbed into k */
365 if ((len[k] = p - pk1) == 0) /* length of adj list of element k*/
366 {
367 Cp[k] = -1; /* k is a root of the tree */
368 w[k] = 0; /* k is now a dead element */
369 }
370 if (elenk != 0) cnz = p; /* free unused space in Lk */
371 }
372
373 /* --- Postordering ----------------------------------------------------- */
374 for (i = 0; i < n; i++) Cp[i] = amd_flip(Cp[i]); /* fix assembly tree */
375 for (j = 0; j <= n; j++) head[j] = -1;
376 for (j = n; j >= 0; j--) /* place unordered nodes in lists */
377 {
378 if (nv[j] > 0) continue; /* skip if j is an element */
379 next[j] = head[Cp[j]]; /* place j in list of its parent */
380 head[Cp[j]] = j;
381 }
382 for (e = n; e >= 0; e--) /* place elements in lists */
383 {
384 if (nv[e] <= 0) continue; /* skip unless e is an element */
385 if (Cp[e] != -1) {
386 next[e] = head[Cp[e]]; /* place e in list of its parent */
387 head[Cp[e]] = e;
388 }
389 }
390 for (k = 0, i = 0; i <= n; i++) /* postorder the assembly tree */
391 {
392 if (Cp[i] == -1) k = internal::cs_tdfs<StorageIndex>(i, k, head, next, perm.indices().data(), w);
393 }
394
395 perm.indices().conservativeResize(n);
396}
397
398} // namespace internal
399
400} // end namespace Eigen
401
402#endif // EIGEN_SPARSE_AMD_H