GNU Octave  8.1.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
__ichol__.cc
Go to the documentation of this file.
1 ////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (C) 2013-2023 The Octave Project Developers
4 //
5 // See the file COPYRIGHT.md in the top-level directory of this
6 // distribution or <https://octave.org/copyright/>.
7 //
8 // This file is part of Octave.
9 //
10 // Octave is free software: you can redistribute it and/or modify it
11 // under the terms of the GNU General Public License as published by
12 // the Free Software Foundation, either version 3 of the License, or
13 // (at your option) any later version.
14 //
15 // Octave is distributed in the hope that it will be useful, but
16 // WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 // GNU General Public License for more details.
19 //
20 // You should have received a copy of the GNU General Public License
21 // along with Octave; see the file COPYING. If not, see
22 // <https://www.gnu.org/licenses/>.
23 //
24 ////////////////////////////////////////////////////////////////////////
25 
26 #if defined (HAVE_CONFIG_H)
27 # include "config.h"
28 #endif
29 
30 #include "oct-locbuf.h"
31 #include "oct-norm.h"
32 
33 #include "defun.h"
34 #include "error.h"
35 
36 #include "builtin-defun-decls.h"
37 
39 
40 // Secondary functions for complex and real case used in ichol algorithms.
42 {
43 #if defined (HAVE_CXX_COMPLEX_SETTERS)
44  b.imag (-b.imag ());
45 #elif defined (HAVE_CXX_COMPLEX_REFERENCE_ACCESSORS)
46  b.imag () = -b.imag ();
47 #else
48  b = b.conj ();
49 #endif
50  return a * b;
51 }
52 
53 double ichol_mult_real (double a, double b)
54 {
55  return a * b;
56 }
57 
59 {
60  if (pivot.imag () != 0)
61  error ("ichol: non-real pivot encountered. The matrix must be Hermitian.");
62  else if (pivot.real () < 0)
63  error ("ichol: negative pivot encountered");
64 
65  return true;
66 }
67 
68 bool ichol_checkpivot_real (double pivot)
69 {
70  if (pivot < 0)
71  error ("ichol: negative pivot encountered");
72 
73  return true;
74 }
75 
76 template <typename octave_matrix_t, typename T, T (*ichol_mult) (T, T),
77  bool (*ichol_checkpivot) (T)>
78 void ichol_0 (octave_matrix_t& sm, const std::string michol = "off")
79 {
80  const octave_idx_type n = sm.cols ();
81  octave_idx_type j1, jend, j2, jrow, jjrow, j, jw, i, k, jj, r;
82  T tl;
83 
84  char opt;
85  enum {OFF, ON};
86  if (michol == "on")
87  opt = ON;
88  else
89  opt = OFF;
90 
91  // Input matrix pointers
92  octave_idx_type *cidx = sm.cidx ();
93  octave_idx_type *ridx = sm.ridx ();
94  T *data = sm.data ();
95 
96  // Working arrays
100  OCTAVE_LOCAL_BUFFER (T, dropsums, n);
101 
102  // Initialize working arrays
103  for (i = 0; i < n; i++)
104  {
105  iw[i] = -1;
106  Llist[i] = -1;
107  Lfirst[i] = -1;
108  dropsums[i] = 0;
109  }
110 
111  // Loop over all columns
112  for (k = 0; k < n; k++)
113  {
114  j1 = cidx[k];
115  j2 = cidx[k+1];
116  for (j = j1; j < j2; j++)
117  iw[ridx[j]] = j;
118 
119  jrow = Llist[k];
120  // Iterate over each non-zero element in the actual row.
121  while (jrow != -1)
122  {
123  jjrow = Lfirst[jrow];
124  jend = cidx[jrow+1];
125  for (jj = jjrow; jj < jend; jj++)
126  {
127  r = ridx[jj];
128  jw = iw[r];
129  tl = ichol_mult (data[jj], data[jjrow]);
130  if (jw != -1)
131  data[jw] -= tl;
132  else
133  // Because of the symmetry of the matrix, we know
134  // the drops in the column r are also in the column k.
135  if (opt == ON)
136  {
137  dropsums[r] -= tl;
138  dropsums[k] -= tl;
139  }
140  }
141  // Update the linked list and the first entry of the actual column.
142  if ((jjrow + 1) < jend)
143  {
144  Lfirst[jrow]++;
145  j = jrow;
146  jrow = Llist[jrow];
147  Llist[j] = Llist[ridx[Lfirst[j]]];
148  Llist[ridx[Lfirst[j]]] = j;
149  }
150  else
151  jrow = Llist[jrow];
152  }
153 
154  if (opt == ON)
155  data[j1] += dropsums[k];
156 
157  // Test for j1 == j2 must be first to avoid invalid ridx[j1] access
158  if (j1 == j2 || ridx[j1] != k)
159  error ("ichol: encountered a pivot equal to 0");
160 
161  if (! ichol_checkpivot (data[j1]))
162  break;
163 
164  data[cidx[k]] = std::sqrt (data[j1]);
165 
166  // Update Llist and Lfirst with the k-column information. Also,
167  // scale the column elements by the pivot and reset the working array iw.
168  if (k < (n - 1))
169  {
170  iw[ridx[j1]] = -1;
171  for (i = j1 + 1; i < j2; i++)
172  {
173  iw[ridx[i]] = -1;
174  data[i] /= data[j1];
175  }
176  Lfirst[k] = j1;
177  if ((Lfirst[k] + 1) < j2)
178  {
179  Lfirst[k]++;
180  jjrow = ridx[Lfirst[k]];
181  Llist[k] = Llist[jjrow];
182  Llist[jjrow] = k;
183  }
184  }
185  }
186 }
187 
188 DEFUN (__ichol0__, args, ,
189  doc: /* -*- texinfo -*-
190 @deftypefn {} {@var{L} =} __ichol0__ (@var{A}, @var{michol})
191 Undocumented internal function.
192 @end deftypefn */)
193 {
194  if (args.length () != 2)
195  print_usage ();
196 
197  std::string michol = args(1).string_value ();
198 
199  // In ICHOL0 algorithm the zero-pattern of the input matrix is preserved
200  // so its structure does not change during the algorithm. The same input
201  // matrix is used to build the output matrix due to that fact.
202  if (! args(0).iscomplex ())
203  {
204  SparseMatrix sm = Ftril (args(0))(0).sparse_matrix_value ();
206  ichol_checkpivot_real> (sm, michol);
207  return ovl (sm);
208  }
209  else
210  {
212  = Ftril (args(0))(0).sparse_complex_matrix_value ();
214  ichol_checkpivot_complex> (sm, michol);
215  return ovl (sm);
216  }
217 }
218 
219 template <typename octave_matrix_t, typename T, T (*ichol_mult) (T, T),
220  bool (*ichol_checkpivot) (T)>
221 void ichol_t (const octave_matrix_t& sm, octave_matrix_t& L, const T *cols_norm,
222  const T droptol, const std::string michol = "off")
223 {
224 
225  const octave_idx_type n = sm.cols ();
226  octave_idx_type j, jrow, jend, jjrow, i, k, jj, total_len,
227  w_len, max_len, ind;
228  char opt;
229  enum {OFF, ON};
230  if (michol == "on")
231  opt = ON;
232  else
233  opt = OFF;
234 
235  // Input matrix pointers
236  octave_idx_type *cidx = sm.cidx ();
237  octave_idx_type *ridx = sm.ridx ();
238  T *data = sm.data ();
239 
240  // Output matrix data structures. Because the final zero pattern pattern of
241  // the output matrix is not known due to fill-in elements, a heuristic
242  // approach has been adopted for memory allocation. The size of ridx_out_l
243  // and data_out_l is incremented 10% of their actual size (nnz (A) in the
244  // beginning). If that amount is less than n, their size is just incremented
245  // in n elements. This way the number of reallocations decreases throughout
246  // the process, obtaining a good performance.
247  max_len = sm.nnz ();
248  max_len += (0.1 * max_len) > n ? 0.1 * max_len : n;
249  Array <octave_idx_type> cidx_out_l (dim_vector (n + 1, 1));
250  octave_idx_type *cidx_l = cidx_out_l.fortran_vec ();
251  Array <octave_idx_type> ridx_out_l (dim_vector (max_len, 1));
252  octave_idx_type *ridx_l = ridx_out_l.fortran_vec ();
253  Array <T> data_out_l (dim_vector (max_len, 1));
254  T *data_l = data_out_l.fortran_vec ();
255 
256  // Working arrays
257  OCTAVE_LOCAL_BUFFER (T, w_data, n);
260  OCTAVE_LOCAL_BUFFER (T, col_drops, n);
261  std::vector<octave_idx_type> vec (n, 0);
262  std::vector<bool> mark (n, false);
263 
264  T zero = T (0);
265  cidx_l[0] = cidx[0];
266  for (i = 0; i < n; i++)
267  {
268  Llist[i] = -1;
269  Lfirst[i] = -1;
270  w_data[i] = 0;
271  col_drops[i] = zero;
272  }
273 
274  total_len = 0;
275  for (k = 0; k < n; k++)
276  {
277  ind = 0;
278  for (j = cidx[k]; j < cidx[k+1]; j++)
279  {
280  w_data[ridx[j]] = data[j];
281  // Mark column index, intentionally outside the if-clause to ensure
282  // that mark[k] will be set to true as well.
283  mark[ridx[j]] = true;
284  if (ridx[j] != k)
285  {
286  vec[ind] = ridx[j];
287  ind++;
288  }
289  }
290  jrow = Llist[k];
291  while (jrow != -1)
292  {
293  jjrow = Lfirst[jrow];
294  jend = cidx_l[jrow+1];
295  for (jj = jjrow; jj < jend; jj++)
296  {
297  j = ridx_l[jj];
298  // If the element in the j position of the row is zero,
299  // then it will become non-zero, so we add it to the
300  // vector that tracks non-zero elements in the working row.
301  if (! mark[j])
302  {
303  mark[j] = true;
304  vec[ind] = j;
305  ind++;
306  }
307  w_data[j] -= ichol_mult (data_l[jj], data_l[jjrow]);
308  }
309  // Update the actual column first element and
310  // update the linked list of the jrow row.
311  if ((jjrow + 1) < jend)
312  {
313  Lfirst[jrow]++;
314  j = jrow;
315  jrow = Llist[jrow];
316  Llist[j] = Llist[ridx_l[Lfirst[j]]];
317  Llist[ridx_l[Lfirst[j]]] = j;
318  }
319  else
320  jrow = Llist[jrow];
321  }
322 
323  // Resizing output arrays
324  if ((max_len - total_len) < n)
325  {
326  max_len += (0.1 * max_len) > n ? 0.1 * max_len : n;
327  data_out_l.resize (dim_vector (max_len, 1));
328  data_l = data_out_l.fortran_vec ();
329  ridx_out_l.resize (dim_vector (max_len, 1));
330  ridx_l = ridx_out_l.fortran_vec ();
331  }
332 
333  // The sorting of the non-zero elements of the working column can be
334  // handled in a couple of ways. The most efficient two I found, are
335  // keeping the elements in an ordered binary search tree dynamically or
336  // keep them unsorted in a vector and at the end of the outer iteration
337  // order them. The last approach exhibits lower execution times.
338  std::sort (vec.begin (), vec.begin () + ind);
339 
340  data_l[total_len] = w_data[k];
341  ridx_l[total_len] = k;
342  w_len = 1;
343 
344  // Extract the non-zero elements of working column and
345  // drop the elements that are lower than droptol * cols_norm[k].
346  for (i = 0; i < ind ; i++)
347  {
348  jrow = vec[i];
349  if (w_data[jrow] != zero)
350  {
351  if (std::abs (w_data[jrow]) < (droptol * cols_norm[k]))
352  {
353  if (opt == ON)
354  {
355  col_drops[k] += w_data[jrow];
356  col_drops[jrow] += w_data[jrow];
357  }
358  }
359  else
360  {
361  data_l[total_len + w_len] = w_data[jrow];
362  ridx_l[total_len + w_len] = jrow;
363  w_len++;
364  }
365  }
366  // Clear mark, vec, and w_data. However, mark[k] is not set to zero.
367  mark[jrow] = false;
368  vec[i] = 0;
369  w_data[jrow] = zero;
370  }
371 
372  // Compensate column sums --> michol option
373  if (opt == ON)
374  data_l[total_len] += col_drops[k];
375 
376  if (data_l[total_len] == zero)
377  error ("ichol: encountered a pivot equal to 0");
378 
379  if (! ichol_checkpivot (data_l[total_len]))
380  break;
381 
382  // Once elements are dropped and compensation of column sums are done,
383  // scale the elements by the pivot.
384  data_l[total_len] = std::sqrt (data_l[total_len]);
385  for (jj = total_len + 1; jj < (total_len + w_len); jj++)
386  data_l[jj] /= data_l[total_len];
387  total_len += w_len;
388  // Check if there are too many elements to be indexed with
389  // octave_idx_type type due to fill-in during the process.
390  if (total_len < 0)
391  error ("ichol: integer overflow. Too many fill-in elements in L");
392 
393  cidx_l[k+1] = cidx_l[k] - cidx_l[0] + w_len;
394 
395  // Update Llist and Lfirst with the k-column information.
396  if (k < (n - 1))
397  {
398  Lfirst[k] = cidx_l[k];
399  if ((Lfirst[k] + 1) < cidx_l[k+1])
400  {
401  Lfirst[k]++;
402  jjrow = ridx_l[Lfirst[k]];
403  Llist[k] = Llist[jjrow];
404  Llist[jjrow] = k;
405  }
406  }
407  }
408 
409  // Build the output matrices
410  L = octave_matrix_t (n, n, total_len);
411 
412  for (i = 0; i <= n; i++)
413  L.cidx (i) = cidx_l[i];
414 
415  for (i = 0; i < total_len; i++)
416  {
417  L.ridx (i) = ridx_l[i];
418  L.data (i) = data_l[i];
419  }
420 }
421 
422 DEFUN (__icholt__, args, ,
423  doc: /* -*- texinfo -*-
424 @deftypefn {} {@var{L} =} __icholt__ (@var{A}, @var{droptol}, @var{michol})
425 Undocumented internal function.
426 @end deftypefn */)
427 {
428  if (args.length () != 3)
429  print_usage ();
430 
431  double droptol = args(1).double_value ();
432  std::string michol = args(2).string_value ();
433 
434  if (! args(0).iscomplex ())
435  {
436  SparseMatrix L;
437  SparseMatrix sm_l = Ftril (args(0))(0).sparse_matrix_value ();
438  RowVector sm_col_norms = xcolnorms (sm_l, 1);
441  (sm_l, L, sm_col_norms.fortran_vec (), droptol, michol);
442 
443  return ovl (L);
444  }
445  else
446  {
449  = Ftril (args(0))(0).sparse_complex_matrix_value ();
450  Array <Complex> cols_norm = xcolnorms (sm_l, 1);
453  (sm_l, L, cols_norm.fortran_vec (),
454  Complex (droptol), michol);
455 
456  return ovl (L);
457  }
458 }
459 
460 /*
461 %!test <*51736>
462 %! k = 4;
463 %! n = 2^k;
464 %! Afull = diag (ones (n,1)) / ...
465 %! 2+triu ([zeros(n,2), [n.^-[1;1;2]*ones(1,n-2);zeros(n-3,n-2)]], 1);
466 %! A = sparse (Afull + Afull.');
467 %! opts.type = "ict";
468 %! opts.droptol = 0;
469 %! L = ichol (A, opts);
470 %! assert (norm (A - L*L.'), 0, 2*eps);
471 */
472 
OCTAVE_END_NAMESPACE(octave)
bool ichol_checkpivot_complex(Complex pivot)
Definition: __ichol__.cc:58
bool ichol_checkpivot_real(double pivot)
Definition: __ichol__.cc:68
double ichol_mult_real(double a, double b)
Definition: __ichol__.cc:53
void ichol_t(const octave_matrix_t &sm, octave_matrix_t &L, const T *cols_norm, const T droptol, const std::string michol="off")
Definition: __ichol__.cc:221
void ichol_0(octave_matrix_t &sm, const std::string michol="off")
Definition: __ichol__.cc:78
Complex ichol_mult_complex(Complex a, Complex b)
Definition: __ichol__.cc:41
OCTARRAY_API void resize(const dim_vector &dv, const T &rfv)
Size of the specified dimension.
Definition: Array-base.cc:1032
OCTARRAY_API T * fortran_vec(void)
Size of the specified dimension.
Definition: Array-base.cc:1766
Vector representing the dimensions (size) of an Array.
Definition: dim-vector.h:94
OCTAVE_BEGIN_NAMESPACE(octave) static octave_value daspk_fcn
OCTINTERP_API void print_usage(void)
Definition: defun-int.h:72
#define DEFUN(name, args_name, nargout_name, doc)
Macro to define a builtin function.
Definition: defun.h:56
void error(const char *fmt,...)
Definition: error.cc:979
class OCTAVE_API SparseMatrix
Definition: mx-fwd.h:55
class OCTAVE_API SparseComplexMatrix
Definition: mx-fwd.h:56
octave_idx_type n
Definition: mx-inlines.cc:753
T * r
Definition: mx-inlines.cc:773
std::complex< double > Complex
Definition: oct-cmplx.h:33
#define OCTAVE_LOCAL_BUFFER(T, buf, size)
Definition: oct-locbuf.h:44
RowVector xcolnorms(const Matrix &m, double p)
Definition: oct-norm.cc:628
octave_value_list ovl(const OV_Args &... args)
Construct an octave_value_list with less typing.
Definition: ovl.h:211
static T abs(T x)
Definition: pr-output.cc:1678
OCTAVE_EXPORT octave_value_list Ftril(const octave_value_list &args, int)
Definition: tril.cc:382