GNU Octave 7.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-2022 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
38OCTAVE_NAMESPACE_BEGIN
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
53double 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
68bool ichol_checkpivot_real (double pivot)
69{
70 if (pivot < 0)
71 error ("ichol: negative pivot encountered");
72
73 return true;
74}
75
76template <typename octave_matrix_t, typename T, T (*ichol_mult) (T, T),
77 bool (*ichol_checkpivot) (T)>
78void 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
188DEFUN (__ichol0__, args, ,
189 doc: /* -*- texinfo -*-
190@deftypefn {} {@var{L} =} __ichol0__ (@var{A}, @var{michol})
191Undocumented 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
219template <typename octave_matrix_t, typename T, T (*ichol_mult) (T, T),
220 bool (*ichol_checkpivot) (T)>
221void 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
422DEFUN (__icholt__, args, ,
423 doc: /* -*- texinfo -*-
424@deftypefn {} {@var{L} =} __icholt__ (@var{A}, @var{droptol}, @var{michol})
425Undocumented 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 ();
440 (sm_l, L, xcolnorms (sm_l, 1).fortran_vec (), droptol, michol);
441
442 return ovl (L);
443 }
444 else
445 {
448 = Ftril (args(0))(0).sparse_complex_matrix_value ();
449 Array <Complex> cols_norm = xcolnorms (sm_l, 1);
452 (sm_l, L, cols_norm.fortran_vec (),
453 Complex (droptol), michol);
454
455 return ovl (L);
456 }
457}
458
459/*
460%!test <*51736>
461%! k = 4;
462%! n = 2^k;
463%! Afull = diag (ones (n,1)) / ...
464%! 2+triu ([zeros(n,2), [n.^-[1;1;2]*ones(1,n-2);zeros(n-3,n-2)]], 1);
465%! A = sparse (Afull + Afull.');
466%! opts.type = "ict";
467%! opts.droptol = 0;
468%! L = ichol (A, opts);
469%! assert (norm (A - L*L.'), 0, 2*eps);
470*/
471
472OCTAVE_NAMESPACE_END
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
OCTAVE_NAMESPACE_BEGIN Complex ichol_mult_complex(Complex a, Complex b)
Definition: __ichol__.cc:41
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
OCTARRAY_API void resize(const dim_vector &dv, const T &rfv)
Size of the specified dimension.
Definition: Array.cc:1010
OCTARRAY_API T * fortran_vec(void)
Size of the specified dimension.
Definition: Array.cc:1744
Vector representing the dimensions (size) of an Array.
Definition: dim-vector.h:94
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:980
class OCTAVE_API SparseMatrix
Definition: mx-fwd.h:55
class OCTAVE_API SparseComplexMatrix
Definition: mx-fwd.h:56
RowVector xcolnorms(const Matrix &m, double p)
Definition: oct-norm.cc:628
std::complex< double > Complex
Definition: oct-cmplx.h:33
#define OCTAVE_LOCAL_BUFFER(T, buf, size)
Definition: oct-locbuf.h:44
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