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