sym-ildl  1.2
Incomplete LDL' factorizations of indefinite symmetric and skew-symmetric matrices.
lilc_matrix_ildl.h
1 #ifndef _LILC_MATRIX_ILDL_H_
2 #define _LILC_MATRIX_ILDL_H_
3 
4 
5 using std::endl;
6 using std::cout;
7 using std::abs;
8 
9 template <class el_type>
10 void lilc_matrix<el_type> :: ildl(lilc_matrix<el_type>& L, block_diag_matrix<el_type>& D, idx_vector_type& perm, const double& fill_factor, const double& tol, const double& pp_tol, int piv_type)
11 {
12 
13  //----------------- initialize temporary variables --------------------//
14  const int ncols = n_cols(); //number of cols in A.
15 
16  int lfil;
17  if (fill_factor > 1e4) lfil = ncols; //just incase users decide to enter a giant fill factor for fun...
18  else lfil = 2*fill_factor*nnz()/ncols; //roughly a factor of 2 since only lower tri. of A is stored
19 
20  const el_type alpha = (1.0+sqrt(17.0))/8.0; //for use in pivoting.
21  el_type w1(-1), wr(-1), d1(-1), dr(-1); //for use in bk-pivoting
22  el_type det_D, D_inv11, D_inv22, D_inv12; //for use in 2x2 pivots
23  el_type l_11, l_12; //for use in 2x2 pivots
24 
25  vector<bool> in_set(ncols, false); //bitset used for unsorted merges
26  swap_struct<el_type> s; //struct containing temp vars used in pivoting.
27 
28  elt_vector_type work(ncols, 0), temp(ncols, 0);
29  idx_vector_type curr_nnzs, temp_nnzs; //non-zeros on current col.
30  curr_nnzs.reserve(ncols); //reserves space for worse case (entire col is non-zero)
31 
32  int count = 0; //the total number of nonzeros stored in L.
33  int i, j, k, r, offset, col_size, col_size2(-1);
34  bool size_two_piv = false; //boolean indicating if the pivot is 2x2 or 1x1
35 
36  //--------------- allocate memory for L and D ------------------//
37  L.resize(ncols, ncols); //allocate a vector of size n for Llist as well
38  D.resize(ncols );
39 
40  //------------------- main loop: factoring begins -------------------------//
41  for (k = 0; k < ncols; k++) {
42 
43  //curr nnz vector starts out empty and is cleared at the end of each loop iteration.
44  //assign nonzeros indices of A(k:n, k) to curr_nnzs
45  curr_nnzs.assign (m_idx[k].begin(), m_idx[k].end());
46 
47  //assign nonzero values of A(k:n, k) to work
48  for (j = 0; j < (int) curr_nnzs.size(); j++) {
49  work[curr_nnzs[j]] = m_x[k][j];
50  }
51  sort(curr_nnzs.begin(), curr_nnzs.end());
52 
53  //--------------begin pivoting--------------//
54  // the pivoting below DEFINITELY needs to be refactored into a separate function
55 
56  //do delayed updates on current column. work = Sum_{i=0}^{k-1} L(k,i) * D(i,i) * L(k:n, i)
57  //(the formula above generalizes to block matrix form in the case of 2x2 pivots).
58  update(k, work, curr_nnzs, L, D, in_set);
59 
60  //store diagonal element in d1. set diagonal element in work vector to 0
61  //since we want to find the maximum off-diagonal element.
62  d1 = work[k];
63  work[k] = 0;
64 
65  //find maximum element in work and store its index in r.
66  w1 = max(work, curr_nnzs, r);
67 
68  if (piv_type == pivot_type::BKP) {
69  //we do partial pivoting here, where we take the first element u in the column that satisfies
70  //|u| > pp_tol*|wi|. for more information, consult "A Partial Pivoting Strategy for Sparse
71  //Symmetric Matrix Decomposition" by J.H. Liu (1987).
72  int t = r; //stores location of u
73  el_type u = w1; //stores value of u
74  for (i = 0; i < (int) curr_nnzs.size(); i++) {
75  if (abs(work[curr_nnzs[i]])-pp_tol*w1 > eps ) {
76  t = curr_nnzs[i];
77  u = work[t];
78  break;
79  }
80  }
81 
82  //bunch-kaufman partial pivoting is used below. for a more detailed reference,
83  //refer to "Accuracy and Stability of Numerical Algorithms." by Higham (2002).
84  //------------------- begin bunch-kaufman pivoting ------------------//
85  if (w1 < eps) {
86  //case 0: do nothing. pivot is k.
87  } else if ( (alpha * w1 - abs(d1)) < eps ) {
88  //case 1: do nothing. pivot is k.
89  } else {
90  //since we are doing partial pivoting, we should treat u and t like wi and r, so
91  //we'll just reassign wi and r. note: this has to go in the else clause since
92  //we still use the old wi for case 0 and case 1.
93  w1 = u;
94  r = t;
95 
96  offset = row_first[r];
97  //assign all nonzero indices and values in A(r, k:r)
98  //( not including A(r,r) ) to temp and temp_nnzs
99  for (j = offset; j < (int) list[r].size(); j++) {
100  temp_nnzs.push_back(list[r][j]);
101  temp[list[r][j]] = coeff(r, list[r][j]);
102  }
103 
104  //assign nonzero indices of A(r:n, r) to temp_nnzs
105  temp_nnzs.insert(temp_nnzs.end(), m_idx[r].begin(), m_idx[r].end());
106 
107  //assign nonzero values of to temp
108  for (j = 0; j < (int) m_idx[r].size(); j++) {
109  temp[m_idx[r][j]] = m_x[r][j];
110  }
111 
112  //perform delayed updates on temp. temp = Sum_{i=0}^{k-1} L(r,i) * D(i,i) * L(k:n, i).
113  //(the formula above generalizes to block matrix form in the case of 2x2 pivots).
114  update(r, temp, temp_nnzs, L, D, in_set);
115 
116  dr = temp[r];
117  temp[r] = 0;
118 
119  //find maximum element in temp.
120  wr = max(temp, temp_nnzs, j);
121 
122  if ((alpha*w1*w1 - abs(d1)*wr) < eps) {
123  //case 2: do nothing. pivot is k.
124 
125  } else if ( (alpha * wr - abs(dr)) < eps) {
126  //case 3: pivot is k with r: 1x1 pivot case.
127  temp[r] = dr;
128  work[k] = d1;
129 
130  //--------pivot A and L ---------//
131  pivot(s, in_set, L, k, r);
132 
133  //----------pivot rest ----------//
134 
135  //permute perm
136  std::swap(perm[k], perm[r]);
137 
138  work.swap(temp); //swap work with temp.
139  std::swap(work[k], work[r]); //swap kth and rth row of work
140 
141  curr_nnzs.swap(temp_nnzs); //swap curr_nnzs with temp_nnzs
142 
143  safe_swap(curr_nnzs, k, r); //swap k and r if they are present in curr_nnzs
144 
145  d1 = work[k];
146  //--------end pivot rest---------//
147 
148  } else {
149  //case 4: pivot is k+1 with r: 2x2 pivot case.
150 
151  //must advance list for 2x2 pivot since we are pivoting on col k+1
152  advance_list(k);
153  //for the same reason as above, we must advance L.first as well
154  L.advance_first(k);
155 
156  //restore diagonal elements in work and temp
157  temp[r] = dr;
158  work[k] = d1;
159 
160  //indicate that pivot is 2x2
161  size_two_piv = true;
162 
163  if (k+1 < r) {
164  //symmetrically permute row/col k+1 and r.
165  pivot(s, in_set, L, k+1, r);
166 
167  //----------pivot rest ----------//
168 
169  //permute perm
170  std::swap(perm[k+1], perm[r]);
171 
172  //swap rows k+1 and r of work and temp
173  std::swap(work[k+1], work[r]);
174  std::swap(temp[k+1], temp[r]);
175 
176  //swap k+1 and r in curr_nnzs and temp_nnzs
177  safe_swap(curr_nnzs, k+1, r);
178  safe_swap(temp_nnzs, k+1, r);
179  }
180 
181  d1 = work[k];
182  dr = temp[k+1];
183  }
184  }
185  //--------------end bkp pivoting--------------//
186  } else if (piv_type == pivot_type::ROOK) {
187  //--------------begin rook pivoting--------------//
188  i = k;
189  work[k] = d1;
190 
191  if (alpha * w1 <= abs(d1) + eps) {
192  // do nothing
193  } else {
194  while (true) {
195  // assign nonzeros indices and values of A(r:n, r) to col_r_nnzs
196  for (idx_it it = temp_nnzs.begin(); it != temp_nnzs.end(); it++) {
197  temp[*it] = 0;
198  }
199  temp_nnzs.clear();
200 
201  offset = row_first[r];
202  //assign all nonzero indices and values in A(r, k:r)
203  //( not including A(r,r) ) to temp and temp_nnzs
204  for (j = offset; j < (int) list[r].size(); j++) {
205  temp_nnzs.push_back(list[r][j]);
206  temp[list[r][j]] = coeff(r, list[r][j]);
207  }
208 
209  //assign nonzero indices of A(r:n, r) to temp_nnzs
210  temp_nnzs.insert(temp_nnzs.end(), m_idx[r].begin(), m_idx[r].end());
211 
212  //assign nonzero values of to temp
213  for (j = 0; j < (int) m_idx[r].size(); j++) {
214  temp[m_idx[r][j]] = m_x[r][j];
215  }
216 
217  //perform delayed updates on temp. temp = Sum_{i=0}^{k-1} L(r,i) * D(i,i) * L(k:n, i).
218  //(the formula above generalizes to block matrix form in the case of 2x2 pivots).
219  update(r, temp, temp_nnzs, L, D, in_set);
220 
221  dr = temp[r];
222  temp[r] = 0;
223 
224  //find maximum element in temp.
225  wr = max(temp, temp_nnzs, j);
226  temp[r] = dr;
227 
228  if (alpha * wr <= abs(dr) + eps) {
229  // swap rows and columns k and r
230  pivot(s, in_set, L, k, r);
231 
232  std::swap(perm[k], perm[r]);
233 
234  std::swap(temp[k], temp[r]);
235  work.swap(temp);
236 
237  safe_swap(temp_nnzs, k, r);
238  curr_nnzs.swap(temp_nnzs);
239 
240  d1 = work[k];
241  break;
242  } else if (abs(w1 - wr) < eps) {
243  size_two_piv = true;
244  // swap rows and columns k and i, k+1 and r
245  if (k != i) {
246  //symmetrically permute row/col k and i.
247  pivot(s, in_set, L, k, i);
248 
249  //----------pivot rest ----------//
250 
251  //permute perm
252  std::swap(perm[k], perm[i]);
253 
254  //swap rows k and i of work and temp
255  std::swap(work[k], work[i]);
256  std::swap(temp[k], temp[i]);
257 
258  //swap k+1 and r in curr_nnzs and temp_nnzs
259  safe_swap(curr_nnzs, k, i);
260  safe_swap(temp_nnzs, k, i);
261 
262  d1 = work[k];
263  }
264 
265  advance_list(k);
266  L.advance_first(k);
267 
268  if (k+1 < r) {
269  //symmetrically permute row/col k+1 and r.
270  pivot(s, in_set, L, k+1, r);
271 
272  //----------pivot rest ----------//
273 
274  //permute perm
275  std::swap(perm[k+1], perm[r]);
276 
277  //swap rows k+1 and r of work and temp
278  std::swap(work[k+1], work[r]);
279  std::swap(temp[k+1], temp[r]);
280 
281  //swap k+1 and r in curr_nnzs and temp_nnzs
282  safe_swap(curr_nnzs, k+1, r);
283  safe_swap(temp_nnzs, k+1, r);
284 
285  dr = temp[k+1];
286  }
287  break;
288  } else {
289  i = r;
290  w1 = wr;
291  r = j;
292  work.swap(temp);
293  curr_nnzs.swap(temp_nnzs);
294  }
295  }
296  }
297  //--------------end rook pivoting--------------//
298  }
299 
300  //erase diagonal element from non-zero indices (to exclude it from being dropped)
301  curr_nnzs.erase(std::remove(curr_nnzs.begin(), curr_nnzs.end(), k), curr_nnzs.end());
302 
303  //performs the dual dropping procedure.
304  if (!size_two_piv) {
305  //perform dual dropping criteria on work
306  drop_tol(work, curr_nnzs, lfil, tol);
307 
308  } else {
309  //erase diagonal 2x2 block from non-zero indices (to exclude it from being dropped)
310  temp_nnzs.erase(std::remove(temp_nnzs.begin(), temp_nnzs.end(), k), temp_nnzs.end());
311  curr_nnzs.erase(std::remove(curr_nnzs.begin(), curr_nnzs.end(), k+1), curr_nnzs.end());
312  temp_nnzs.erase(std::remove(temp_nnzs.begin(), temp_nnzs.end(), k+1), temp_nnzs.end());
313 
314  //compute inverse of the 2x2 block diagonal pivot.
315  det_D = d1*dr - work[k+1]*work[k+1];
316  if ( abs(det_D) < eps) det_D = 1e-6; //statically pivot;
317  D_inv11 = dr/det_D;
318  D_inv22 = d1/det_D;
319  D_inv12 = -work[k+1]/det_D;
320 
321  //assign pivot to D (d1 is assigned to D(k,k) later)
322  D.off_diagonal(k) = work[k+1];
323  D[k+1] = dr;
324 
325  //merge nonzeros of curr and temp together so iterating through them will be easier
326  unordered_inplace_union(curr_nnzs, temp_nnzs.begin(), temp_nnzs.end(), in_set);
327 
328 
329  //multiply inverse of pivot to work and temp (gives us two columns of l)
330  for (idx_it it = curr_nnzs.begin(); it != curr_nnzs.end(); it++) {
331  l_11 = work[*it]*D_inv11 + temp[*it]*D_inv12;
332  l_12 = work[*it]*D_inv12 + temp[*it]*D_inv22;
333 
334  //note that work and temp roughly share the same non-zero indices
335  work[*it] = l_11;
336  temp[*it] = l_12;
337  }
338 
339  //since the work and temp non-zero indices are roughly the same,
340  //we can copy it over to temp_nnzs
341  temp_nnzs.assign(curr_nnzs.begin(), curr_nnzs.end());
342 
343  //perform dual dropping procedure on work and temp
344  drop_tol(temp, temp_nnzs, lfil, tol);
345  drop_tol(work, curr_nnzs, lfil, tol);
346 
347 
348  }
349 
350  //resize kth column of L to proper size.
351  L.m_idx[k].resize(curr_nnzs.size()+1);
352  L.m_x[k].resize(curr_nnzs.size()+1);
353 
354  //assign diagonal element to D
355  D[k] = d1;
356 
357  //assign 1s to diagonal of L.
358  L.m_x[k][0] = 1;
359  L.m_idx[k][0] = k;
360  count++;
361 
362  if (!size_two_piv) {
363  if ( abs(D[k]) < eps) D[k] = 1e-6; //statically pivot
364  i = 1;
365  for (idx_it it = curr_nnzs.begin(); it != curr_nnzs.end(); it++) {
366  if ( abs(work[*it]) > eps) {
367  L.m_idx[k][i] = *it; //col k nonzero indices of L are stored
368  L.m_x[k][i] = work[*it]/D[k]; //col k nonzero values of L are stored
369 
370  L.list[*it].push_back(k); //update Llist
371  count++;
372  i++;
373  }
374  }
375 
376  col_size = i;
377 
378  //advance list and L.first
379  L.advance_first(k);
380  advance_list(k);
381  } else {
382  //resize k+1th column of L to proper size.
383  L.m_idx[k+1].resize(temp_nnzs.size()+1);
384  L.m_x[k+1].resize(temp_nnzs.size()+1);
385 
386  //assign 1s to diagonal of L.
387  L.m_x[k+1][0] = 1;
388  L.m_idx[k+1][0] = k+1;
389  count++;
390 
391  i = 1;
392  for (idx_it it = curr_nnzs.begin(); it != curr_nnzs.end(); it++) {
393  if ( abs(work[*it]) > eps) {
394  L.m_x[k][i] = work[*it]; //col k nonzero indices of L are stored
395  L.m_idx[k][i] = *it; //col k nonzero values of L are stored
396 
397  L.list[*it].push_back(k); //update L.list
398  count++;
399  i++;
400  }
401 
402  }
403 
404  j = 1;
405  for (idx_it it = temp_nnzs.begin(); it != temp_nnzs.end(); it++) {
406  if ( abs(temp[*it]) > eps) {
407  L.m_x[k+1][j] = temp[*it]; //col k+1 nonzero indices of L are stored
408  L.m_idx[k+1][j] = *it; //col k+1 nonzero values of L are stored
409 
410  L.list[*it].push_back(k+1); //update L.list
411  count++;
412  j++;
413  }
414 
415  }
416 
417  col_size = i;
418  col_size2 = j;
419 
420  //update list and L.first
421  L.advance_first(k+1);
422  advance_list(k+1);
423 
424  }
425 
426  // ------------- reset temp and work back to zero -----------------//
427  work[k] = 0;
428  temp[k] = 0;
429 
430  if (k + 1 < ncols) {
431  temp[k+1] = 0;
432  work[k+1] = 0;
433  }
434 
435  for (idx_it it = curr_nnzs.begin(); it != curr_nnzs.end(); it++) {
436  work[*it] = 0;
437  }
438  curr_nnzs.clear(); //zero out work vector
439 
440  for (idx_it it = temp_nnzs.begin(); it != temp_nnzs.end(); it++) {
441  temp[*it] = 0;
442  }
443  temp_nnzs.clear(); //zero out work vector
444 
445  //-------------------------------------------------------------------//
446 
447  //resize columns of L to correct size
448  L.m_x[k].resize(col_size);
449  L.m_idx[k].resize(col_size);
450 
451  if (size_two_piv) {
452  L.m_x[k+1].resize(col_size2);
453  L.m_idx[k+1].resize(col_size2);
454  k++;
455 
456  size_two_piv = false;
457  }
458  }
459 
460  //assign number of non-zeros in L to L.nnz_count
461  L.nnz_count = count;
462 
463 }
464 
465 #endif
int nnz_count
Number of nonzeros in the matrix.
Definition: lil_sparse_matrix.h:32
el_type & off_diagonal(int i)
Definition: block_diag_matrix.h:114
vector< elt_vector_type > m_x
The values of the nonzeros in the matrix.
Definition: lil_sparse_matrix.h:36
vector< idx_vector_type > m_idx
The row/col indices. The way m_idx is used depends on whether the matrix is in LIL-C or LIL-R...
Definition: lil_sparse_matrix.h:35
void resize(int n, el_type default_value)
Resizes this matrix to an n*n matrix with default_value on the main diagonal.
Definition: block_diag_matrix.h:70
void advance_first(const int &k)
Updates A.first for iteration k.
Definition: lilc_matrix_declarations.h:328
A list-of-lists (LIL) matrix in column oriented format.
Definition: lilc_matrix.h:9
A quick implementation of a diagonal matrix with 1x1 and 2x2 blocks.
Definition: block_diag_matrix.h:39
A structure containing variables used in pivoting a LIL-C matrix.
Definition: swap_struct.h:6
void resize(int n_rows, int n_cols)
Resizes the matrix. For use in preallocating space before factorization begins.
Definition: lilc_matrix_declarations.h:119
std::vector< std::vector< int > > list
A list of linked lists that gives the non-zero elements in each row of A. Since at any time we may sw...
Definition: lilc_matrix_declarations.h:44
void ildl(lilc_matrix< el_type > &L, block_diag_matrix< el_type > &D, idx_vector_type &perm, const double &fill_factor, const double &tol, const double &pp_tol, int piv_type=pivot_type::BKP)
Performs an LDL&#39; factorization of this matrix.
Definition: lilc_matrix_ildl.h:10