Coverage Report

Created: 2025-10-28 07:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/igraph/src/linalg/arpack.c
Line
Count
Source
1
/* vim:set ts=4 sw=4 sts=4 noet: */
2
/*
3
   igraph library.
4
   Copyright (C) 2007-2012  Gabor Csardi <csardi.gabor@gmail.com>
5
   334 Harvard street, Cambridge, MA 02139 USA
6
7
   This program is free software; you can redistribute it and/or modify
8
   it under the terms of the GNU General Public License as published by
9
   the Free Software Foundation; either version 2 of the License, or
10
   (at your option) any later version.
11
12
   This program is distributed in the hope that it will be useful,
13
   but WITHOUT ANY WARRANTY; without even the implied warranty of
14
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
   GNU General Public License for more details.
16
17
   You should have received a copy of the GNU General Public License
18
   along with this program; if not, write to the Free Software
19
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20
   02110-1301 USA
21
22
*/
23
24
#include "igraph_arpack.h"
25
26
#include "core/interruption.h"
27
#include "linalg/arpack_internal.h"
28
29
#include "igraph_memory.h"
30
#include "igraph_random.h"
31
32
#include <math.h>
33
#include <stdio.h>
34
#include <string.h>
35
#include <limits.h>
36
37
/* The ARPACK example file dssimp.f is used as a template */
38
39
static igraph_arpack_error_t igraph_i_arpack_err_dsaupd(int error);
40
static igraph_arpack_error_t igraph_i_arpack_err_dseupd(int error);
41
static igraph_arpack_error_t igraph_i_arpack_err_dnaupd(int error);
42
static igraph_arpack_error_t igraph_i_arpack_err_dneupd(int error);
43
44
static igraph_arpack_error_t last_arpack_error = IGRAPH_ARPACK_NO_ERROR;
45
46
0
#define IGRAPH_ARPACK_ERROR(code) { \
47
0
    last_arpack_error = code; \
48
0
    IGRAPH_ERROR(igraph_arpack_error_to_string(code), IGRAPH_EARPACK); \
49
0
}
50
51
/* Pristine ARPACK options object that is not exposed to the user; this is used
52
 * as a template for \c igraph_i_arpack_options_default when the user requests
53
 * a pointer to the default object */
54
static const igraph_arpack_options_t igraph_i_arpack_options_pristine = {
55
    /* .bmat = */ { 'I' },
56
    /* .n = */ 0,
57
    /* .which = */ { 'X', 'X' },
58
    /* .nev = */ 1,
59
    /* .tol = */ 0,
60
    /* .ncv = */ 0, /* 0 means "automatic" */
61
    /* .ldv = */ 0,
62
    /* .ishift = */ 1,
63
    /* .mxiter = */ 3000,
64
    /* .nb = */ 1,
65
    /* .mode = */ 1,
66
    /* .start = */ 0,
67
    /* .lworl = */ 0,
68
    /* .sigma = */ 0,
69
    /* .sigmai = */ 0,
70
    /* .info = */ 0,
71
    /* .ierr = */ 0,
72
    /* .noiter = */ 0,
73
    /* .nconv = */ 0,
74
    /* .numop = */ 0,
75
    /* .numopb = */ 0,
76
    /* .numreo = */ 0,
77
    /* .iparam = */ {
78
        /* same as ishift: */ 1,
79
        0,
80
        /* same as mxiter: */ 3000,
81
        /* same as nb: */ 1,
82
        0,
83
        0,
84
        /* same as mode: */ 1
85
        /* the rest are all zeros */
86
    },
87
    /* .ipntr = */ { 0 /* the rest are all zeros */ }
88
};
89
90
static IGRAPH_THREAD_LOCAL igraph_arpack_options_t igraph_i_arpack_options_default;
91
92
/**
93
 * \function igraph_arpack_options_init
94
 * \brief Initialize ARPACK options.
95
 *
96
 * Initializes ARPACK options, set them to default values.
97
 * You can always pass the initialized \ref igraph_arpack_options_t
98
 * object to built-in igraph functions without any modification. The
99
 * built-in igraph functions modify the options to perform their
100
 * calculation, e.g. \ref igraph_pagerank() always searches for the
101
 * eigenvalue with the largest magnitude, regardless of the supplied
102
 * value.
103
 *
104
 * </para><para>
105
 * If you want to implement your own function involving eigenvalue
106
 * calculation using ARPACK, however, you will likely need to set up
107
 * the fields for yourself.
108
 *
109
 * \param o The \ref igraph_arpack_options_t object to initialize.
110
 *
111
 * Time complexity: O(1).
112
 */
113
114
0
void igraph_arpack_options_init(igraph_arpack_options_t *o) {
115
0
    *o = igraph_i_arpack_options_pristine;
116
117
0
    o->bmat[0] = 'I';
118
0
    o->n = 0;         /* needs to be updated! */
119
0
    o->which[0] = 'X'; o->which[1] = 'X';
120
0
    o->nev = 1;
121
0
    o->tol = 0;
122
0
    o->ncv = 0;       /* 0 means "automatic" */
123
0
    o->ldv = o->n;        /* will be updated to (real) n */
124
0
    o->ishift = 1;
125
0
    o->mxiter = 3000;
126
0
    o->nb = 1;
127
0
    o->mode = 1;
128
0
    o->start = 0;
129
0
    o->lworkl = 0;
130
0
    o->sigma = 0;
131
0
    o->sigmai = 0;
132
0
    o->info = o->start;
133
134
0
    o->iparam[0] = o->ishift; o->iparam[1] = 0; o->iparam[2] = o->mxiter; o->iparam[3] = o->nb;
135
0
    o->iparam[4] = 0; o->iparam[5] = 0; o->iparam[6] = o->mode; o->iparam[7] = 0;
136
0
    o->iparam[8] = 0; o->iparam[9] = 0; o->iparam[10] = 0;
137
0
}
138
139
/**
140
 * \function igraph_arpack_options_get_default
141
 * \brief Returns a pointer to a "default" ARPACK options object.
142
 *
143
 * This function is used by other igraph functions taking an \ref igraph_arpack_options_t
144
 * object as an argument to get a reference to a pre-initialized "default"
145
 * ARPACK options object when the user passes \c NULL instead of a real ARPACK
146
 * options object. The object returned from this function is reset to a pristine
147
 * state with every call to \c igraph_arpack_options_get_default().
148
 *
149
 * </para><para>
150
 * The object returned from this function must \em not be destroyed.
151
 *
152
 * Time complexity: O(1).
153
 */
154
0
igraph_arpack_options_t* igraph_arpack_options_get_default(void) {
155
0
    igraph_i_arpack_options_default = igraph_i_arpack_options_pristine;
156
0
    return &igraph_i_arpack_options_default;
157
0
}
158
159
/**
160
 * \function igraph_arpack_storage_init
161
 * \brief Initialize ARPACK storage.
162
 *
163
 * You only need this function if you want to run multiple eigenvalue
164
 * calculations using ARPACK, and want to spare the memory
165
 * allocation/deallocation between each two runs. Otherwise it is safe
166
 * to supply a null pointer as the \c storage argument of both \ref
167
 * igraph_arpack_rssolve() and \ref igraph_arpack_rnsolve() to make
168
 * memory allocated and deallocated automatically.
169
 *
170
 * </para><para>
171
 * Don't forget to call the \ref igraph_arpack_storage_destroy()
172
 * function on the storage object if you don't need it any more.
173
 *
174
 * \param s The \ref igraph_arpack_storage_t object to initialize.
175
 * \param maxn The maximum order of the matrices.
176
 * \param maxncv The maximum NCV parameter intended to use.
177
 * \param maxldv The maximum LDV parameter intended to use.
178
 * \param symm Whether symmetric or non-symmetric problems will be
179
 *    solved using this \ref igraph_arpack_storage_t. (You cannot use
180
 *    the same storage both with symmetric and non-symmetric solvers.)
181
 * \return Error code.
182
 *
183
 * Time complexity: O(maxncv*(maxldv+maxn)).
184
 */
185
186
igraph_error_t igraph_arpack_storage_init(igraph_arpack_storage_t *s, igraph_int_t maxn,
187
                               igraph_int_t maxncv, igraph_int_t maxldv,
188
0
                               igraph_bool_t symm) {
189
190
    /* TODO: check arguments */
191
0
    if (maxn > INT_MAX) {
192
0
        IGRAPH_ERROR("Maximum order of matrices too large for ARPACK.", IGRAPH_EOVERFLOW);
193
0
    }
194
0
    if (maxncv > INT_MAX) {
195
0
        IGRAPH_ERROR("Maximum NCV parameter too large for ARPACK.", IGRAPH_EOVERFLOW);
196
0
    }
197
0
    if (maxldv > INT_MAX) {
198
0
        IGRAPH_ERROR("Maximum LDV parameter too large for ARPACK.", IGRAPH_EOVERFLOW);
199
0
    }
200
201
0
    s->maxn = (int) maxn;
202
0
    s->maxncv = (int) maxncv;
203
0
    s->maxldv = (int) maxldv;
204
205
0
#define CHECKMEM(x) \
206
0
    if (!x) { \
207
0
        IGRAPH_ERROR("Cannot allocate memory for ARPACK", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ \
208
0
    } \
209
0
    IGRAPH_FINALLY(igraph_free, x);
210
211
0
    s->v = IGRAPH_CALLOC(maxldv * maxncv, igraph_real_t); CHECKMEM(s->v);
212
0
    s->workd = IGRAPH_CALLOC(3 * maxn, igraph_real_t); CHECKMEM(s->workd);
213
0
    s->d = IGRAPH_CALLOC(2 * maxncv, igraph_real_t); CHECKMEM(s->d);
214
0
    s->resid = IGRAPH_CALLOC(maxn, igraph_real_t); CHECKMEM(s->resid);
215
0
    s->ax = IGRAPH_CALLOC(maxn, igraph_real_t); CHECKMEM(s->ax);
216
0
    s->select = IGRAPH_CALLOC(maxncv, int); CHECKMEM(s->select);
217
218
0
    if (symm) {
219
0
        s->workl = IGRAPH_CALLOC(maxncv * (maxncv + 8), igraph_real_t); CHECKMEM(s->workl);
220
0
        s->di = 0;
221
0
        s->workev = 0;
222
0
    } else {
223
0
        s->workl = IGRAPH_CALLOC(3 * maxncv * (maxncv + 2), igraph_real_t); CHECKMEM(s->workl);
224
0
        s->di = IGRAPH_CALLOC(2 * maxncv, igraph_real_t); CHECKMEM(s->di);
225
0
        s->workev = IGRAPH_CALLOC(3 * maxncv, igraph_real_t); CHECKMEM(s->workev);
226
0
        IGRAPH_FINALLY_CLEAN(2);
227
0
    }
228
229
0
#undef CHECKMEM
230
231
0
    IGRAPH_FINALLY_CLEAN(7);
232
0
    return IGRAPH_SUCCESS;
233
0
}
234
235
/**
236
 * \function igraph_arpack_storage_destroy
237
 * \brief Deallocate ARPACK storage.
238
 *
239
 * \param s The \ref igraph_arpack_storage_t object for which the
240
 *    memory will be deallocated.
241
 *
242
 * Time complexity: operating system dependent.
243
 */
244
245
0
void igraph_arpack_storage_destroy(igraph_arpack_storage_t *s) {
246
247
0
    if (s->di) {
248
0
        IGRAPH_FREE(s->di);
249
0
    }
250
0
    if (s->workev) {
251
0
        IGRAPH_FREE(s->workev);
252
0
    }
253
254
0
    IGRAPH_FREE(s->workl);
255
0
    IGRAPH_FREE(s->select);
256
0
    IGRAPH_FREE(s->ax);
257
0
    IGRAPH_FREE(s->resid);
258
0
    IGRAPH_FREE(s->d);
259
0
    IGRAPH_FREE(s->workd);
260
0
    IGRAPH_FREE(s->v);
261
0
}
262
263
/**
264
 * "Solver" for 1x1 eigenvalue problems since ARPACK sometimes blows up with
265
 * these.
266
 */
267
static igraph_error_t igraph_i_arpack_rssolve_1x1(igraph_arpack_function_t *fun, void *extra,
268
                                       igraph_arpack_options_t* options,
269
0
                                       igraph_vector_t* values, igraph_matrix_t* vectors) {
270
0
    igraph_real_t a, b;
271
0
    int nev = options->nev;
272
273
0
    if (nev <= 0) {
274
0
        IGRAPH_ARPACK_ERROR(IGRAPH_ARPACK_NEVNPOS);
275
0
    }
276
277
    /* Probe the value in the matrix */
278
0
    a = 1;
279
0
    IGRAPH_CHECK(fun(&b, &a, 1, extra));
280
281
0
    options->nconv = nev;
282
283
0
    if (values != 0) {
284
0
        IGRAPH_CHECK(igraph_vector_resize(values, 1));
285
0
        VECTOR(*values)[0] = b;
286
0
    }
287
288
0
    if (vectors != 0) {
289
0
        IGRAPH_CHECK(igraph_matrix_resize(vectors, 1, 1));
290
0
        MATRIX(*vectors, 0, 0) = 1;
291
0
    }
292
293
0
    return IGRAPH_SUCCESS;
294
0
}
295
296
/**
297
 * "Solver" for 1x1 eigenvalue problems since ARPACK sometimes blows up with
298
 * these.
299
 */
300
static igraph_error_t igraph_i_arpack_rnsolve_1x1(igraph_arpack_function_t *fun, void *extra,
301
                                       igraph_arpack_options_t* options,
302
0
                                       igraph_matrix_t* values, igraph_matrix_t* vectors) {
303
0
    igraph_real_t a, b;
304
0
    int nev = options->nev;
305
306
0
    if (nev <= 0) {
307
0
        IGRAPH_ARPACK_ERROR(IGRAPH_ARPACK_NEVNPOS);
308
0
    }
309
310
    /* Probe the value in the matrix */
311
0
    a = 1;
312
0
    IGRAPH_CHECK(fun(&b, &a, 1, extra));
313
314
0
    options->nconv = nev;
315
316
0
    if (values != 0) {
317
0
        IGRAPH_CHECK(igraph_matrix_resize(values, 1, 2));
318
0
        MATRIX(*values, 0, 0) = b; MATRIX(*values, 0, 1) = 0;
319
0
    }
320
321
0
    if (vectors != 0) {
322
0
        IGRAPH_CHECK(igraph_matrix_resize(vectors, 1, 1));
323
0
        MATRIX(*vectors, 0, 0) = 1;
324
0
    }
325
326
0
    return IGRAPH_SUCCESS;
327
0
}
328
329
/**
330
 * "Solver" for 2x2 nonsymmetric eigenvalue problems since ARPACK sometimes
331
 * blows up with these.
332
 */
333
static igraph_error_t igraph_i_arpack_rnsolve_2x2(igraph_arpack_function_t *fun, void *extra,
334
                                       igraph_arpack_options_t* options, igraph_matrix_t* values,
335
0
                                       igraph_matrix_t* vectors) {
336
0
    igraph_real_t vec[2], mat[4];
337
0
    igraph_real_t a, b, c, d;
338
0
    igraph_real_t trace, det, tsq4_minus_d;
339
0
    igraph_complex_t eval1, eval2;
340
0
    igraph_complex_t evec1[2], evec2[2];
341
0
    igraph_bool_t swap_evals = false;
342
0
    igraph_bool_t complex_evals = false;
343
0
    int nev = options->nev;
344
345
0
    if (nev <= 0) {
346
0
        IGRAPH_ARPACK_ERROR(IGRAPH_ARPACK_NEVNPOS);
347
0
    }
348
0
    if (nev > 2) {
349
0
        nev = 2;
350
0
    }
351
352
    /* Probe the values in the matrix */
353
0
    vec[0] = 1; vec[1] = 0;
354
0
    IGRAPH_CHECK(fun(mat, vec, 2, extra));
355
0
    vec[0] = 0; vec[1] = 1;
356
0
    IGRAPH_CHECK(fun(mat + 2, vec, 2, extra));
357
0
    a = mat[0]; b = mat[2]; c = mat[1]; d = mat[3];
358
359
    /* Get the trace and the determinant */
360
0
    trace = a + d;
361
0
    det = a * d - b * c;
362
0
    tsq4_minus_d = trace * trace / 4 - det;
363
364
    /* Calculate the eigenvalues */
365
0
    complex_evals = tsq4_minus_d < 0;
366
0
    eval1 = igraph_complex_sqrt_real(tsq4_minus_d);
367
0
    if (complex_evals) {
368
0
        eval2 = igraph_complex_mul_real(eval1, -1);
369
0
    } else {
370
        /* to avoid having -0 in the imaginary part */
371
0
        eval2 = igraph_complex(-IGRAPH_REAL(eval1), 0);
372
0
    }
373
0
    eval1 = igraph_complex_add_real(eval1, trace / 2);
374
0
    eval2 = igraph_complex_add_real(eval2, trace / 2);
375
376
0
    if (c != 0) {
377
0
        evec1[0] = igraph_complex_sub_real(eval1, d);
378
0
        evec1[1] = igraph_complex(c, 0);
379
0
        evec2[0] = igraph_complex_sub_real(eval2, d);
380
0
        evec2[1] = igraph_complex(c, 0);
381
0
    } else if (b != 0) {
382
0
        evec1[0] = igraph_complex(b, 0);
383
0
        evec1[1] = igraph_complex_sub_real(eval1, a);
384
0
        evec2[0] = igraph_complex(b, 0);
385
0
        evec2[1] = igraph_complex_sub_real(eval2, a);
386
0
    } else {
387
0
        evec1[0] = igraph_complex(1, 0);
388
0
        evec1[1] = igraph_complex(0, 0);
389
0
        evec2[0] = igraph_complex(0, 0);
390
0
        evec2[1] = igraph_complex(1, 0);
391
0
    }
392
393
    /* Sometimes we have to swap eval1 with eval2 and evec1 with eval2;
394
     * determine whether we have to do it now */
395
0
    if (options->which[0] == 'S') {
396
0
        if (options->which[1] == 'M') {
397
            /* eval1 must be the one with the smallest magnitude */
398
0
            swap_evals = (igraph_complex_abs(eval1) > igraph_complex_abs(eval2));
399
0
        } else if (options->which[1] == 'R') {
400
            /* eval1 must be the one with the smallest real part */
401
0
            swap_evals = (IGRAPH_REAL(eval1) > IGRAPH_REAL(eval2));
402
0
        } else if (options->which[1] == 'I') {
403
            /* eval1 must be the one with the smallest imaginary part */
404
0
            swap_evals = (IGRAPH_IMAG(eval1) > IGRAPH_IMAG(eval2));
405
0
        } else {
406
0
            IGRAPH_ARPACK_ERROR(IGRAPH_ARPACK_WHICHINV);
407
0
        }
408
0
    } else if (options->which[0] == 'L') {
409
0
        if (options->which[1] == 'M') {
410
            /* eval1 must be the one with the largest magnitude */
411
0
            swap_evals = (igraph_complex_abs(eval1) < igraph_complex_abs(eval2));
412
0
        } else if (options->which[1] == 'R') {
413
            /* eval1 must be the one with the largest real part */
414
0
            swap_evals = (IGRAPH_REAL(eval1) < IGRAPH_REAL(eval2));
415
0
        } else if (options->which[1] == 'I') {
416
            /* eval1 must be the one with the largest imaginary part */
417
0
            swap_evals = (IGRAPH_IMAG(eval1) < IGRAPH_IMAG(eval2));
418
0
        } else {
419
0
            IGRAPH_ARPACK_ERROR(IGRAPH_ARPACK_WHICHINV);
420
0
        }
421
0
    } else if (options->which[0] == 'X' && options->which[1] == 'X') {
422
        /* No preference on the ordering of eigenvectors */
423
0
    } else {
424
        /* fprintf(stderr, "%c%c\n", options->which[0], options->which[1]); */
425
0
        IGRAPH_ARPACK_ERROR(IGRAPH_ARPACK_WHICHINV);
426
0
    }
427
428
0
    options->nconv = nev;
429
430
0
    if (swap_evals) {
431
0
        igraph_complex_t dummy;
432
0
        dummy = eval1; eval1 = eval2; eval2 = dummy;
433
0
        dummy = evec1[0]; evec1[0] = evec2[0]; evec2[0] = dummy;
434
0
        dummy = evec1[1]; evec1[1] = evec2[1]; evec2[1] = dummy;
435
0
    }
436
437
0
    if (complex_evals) {
438
        /* The eigenvalues are conjugate pairs, so we store only the
439
         * one with positive imaginary part */
440
0
        if (IGRAPH_IMAG(eval1) < 0) {
441
0
            eval1 = eval2;
442
0
            evec1[0] = evec2[0]; evec1[1] = evec2[1];
443
0
        }
444
0
    }
445
446
0
    if (values != 0) {
447
0
        IGRAPH_CHECK(igraph_matrix_resize(values, nev, 2));
448
0
        MATRIX(*values, 0, 0) = IGRAPH_REAL(eval1);
449
0
        MATRIX(*values, 0, 1) = IGRAPH_IMAG(eval1);
450
0
        if (nev > 1) {
451
0
            MATRIX(*values, 1, 0) = IGRAPH_REAL(eval2);
452
0
            MATRIX(*values, 1, 1) = IGRAPH_IMAG(eval2);
453
0
        }
454
0
    }
455
456
0
    if (vectors != 0) {
457
0
        if (complex_evals) {
458
0
            IGRAPH_CHECK(igraph_matrix_resize(vectors, 2, 2));
459
0
            MATRIX(*vectors, 0, 0) = IGRAPH_REAL(evec1[0]);
460
0
            MATRIX(*vectors, 1, 0) = IGRAPH_REAL(evec1[1]);
461
0
            MATRIX(*vectors, 0, 1) = IGRAPH_IMAG(evec1[0]);
462
0
            MATRIX(*vectors, 1, 1) = IGRAPH_IMAG(evec1[1]);
463
0
        } else {
464
0
            IGRAPH_CHECK(igraph_matrix_resize(vectors, 2, nev));
465
0
            MATRIX(*vectors, 0, 0) = IGRAPH_REAL(evec1[0]);
466
0
            MATRIX(*vectors, 1, 0) = IGRAPH_REAL(evec1[1]);
467
0
            if (nev > 1) {
468
0
                MATRIX(*vectors, 0, 1) = IGRAPH_REAL(evec2[0]);
469
0
                MATRIX(*vectors, 1, 1) = IGRAPH_REAL(evec2[1]);
470
0
            }
471
0
        }
472
0
    }
473
474
0
    return IGRAPH_SUCCESS;
475
0
}
476
477
/**
478
 * "Solver" for symmetric 2x2 eigenvalue problems since ARPACK sometimes blows
479
 * up with these.
480
 */
481
static igraph_error_t igraph_i_arpack_rssolve_2x2(igraph_arpack_function_t *fun, void *extra,
482
                                       igraph_arpack_options_t* options, igraph_vector_t* values,
483
0
                                       igraph_matrix_t* vectors) {
484
0
    igraph_real_t vec[2], mat[4];
485
0
    igraph_real_t a, b, c, d;
486
0
    igraph_real_t trace, det, tsq4_minus_d;
487
0
    igraph_real_t eval1, eval2;
488
0
    int nev = options->nev;
489
490
0
    if (nev <= 0) {
491
0
        IGRAPH_ARPACK_ERROR(IGRAPH_ARPACK_NEVNPOS);
492
0
    }
493
0
    if (nev > 2) {
494
0
        nev = 2;
495
0
    }
496
497
    /* Probe the values in the matrix */
498
0
    vec[0] = 1; vec[1] = 0;
499
0
    IGRAPH_CHECK(fun(mat, vec, 2, extra));
500
0
    vec[0] = 0; vec[1] = 1;
501
0
    IGRAPH_CHECK(fun(mat + 2, vec, 2, extra));
502
0
    a = mat[0]; b = mat[2]; c = mat[1]; d = mat[3];
503
504
    /* Get the trace and the determinant */
505
0
    trace = a + d;
506
0
    det = a * d - b * c;
507
0
    tsq4_minus_d = trace * trace / 4 - det;
508
509
0
    if (tsq4_minus_d >= 0) {
510
        /* Both eigenvalues are real */
511
0
        eval1 = trace / 2 + sqrt(tsq4_minus_d);
512
0
        eval2 = trace / 2 - sqrt(tsq4_minus_d);
513
0
        if (c != 0) {
514
0
            mat[0] = eval1 - d; mat[2] = eval2 - d;
515
0
            mat[1] = c;       mat[3] = c;
516
0
        } else if (b != 0) {
517
0
            mat[0] = b;       mat[2] = b;
518
0
            mat[1] = eval1 - a; mat[3] = eval2 - a;
519
0
        } else {
520
0
            mat[0] = 1; mat[2] = 0;
521
0
            mat[1] = 0; mat[3] = 1;
522
0
        }
523
0
    } else {
524
        /* Both eigenvalues are complex. Should not happen with symmetric
525
         * matrices. */
526
0
        IGRAPH_ERROR("ARPACK error, 2x2 matrix is not symmetric", IGRAPH_EINVAL);
527
0
    }
528
529
    /* eval1 is always the larger eigenvalue. If we want the smaller
530
     * one, we have to swap eval1 with eval2 and also the columns of mat */
531
0
    if (options->which[0] == 'S') {
532
0
        trace = eval1; eval1 = eval2; eval2 = trace;
533
0
        trace = mat[0]; mat[0] = mat[2]; mat[2] = trace;
534
0
        trace = mat[1]; mat[1] = mat[3]; mat[3] = trace;
535
0
    } else if (options->which[0] == 'L' || options->which[0] == 'B') {
536
        /* Nothing to do here */
537
0
    } else if (options->which[0] == 'X' && options->which[1] == 'X') {
538
        /* No preference on the ordering of eigenvectors */
539
0
    } else {
540
0
        IGRAPH_ARPACK_ERROR(IGRAPH_ARPACK_WHICHINV);
541
0
    }
542
543
0
    options->nconv = nev;
544
545
0
    if (values != 0) {
546
0
        IGRAPH_CHECK(igraph_vector_resize(values, nev));
547
0
        VECTOR(*values)[0] = eval1;
548
0
        if (nev > 1) {
549
0
            VECTOR(*values)[1] = eval2;
550
0
        }
551
0
    }
552
553
0
    if (vectors != 0) {
554
0
        IGRAPH_CHECK(igraph_matrix_resize(vectors, 2, nev));
555
0
        MATRIX(*vectors, 0, 0) = mat[0];
556
0
        MATRIX(*vectors, 1, 0) = mat[1];
557
0
        if (nev > 1) {
558
0
            MATRIX(*vectors, 0, 1) = mat[2];
559
0
            MATRIX(*vectors, 1, 1) = mat[3];
560
0
        }
561
0
    }
562
563
0
    return IGRAPH_SUCCESS;
564
0
}
565
566
igraph_error_t igraph_arpack_rssort(igraph_vector_t *values, igraph_matrix_t *vectors,
567
                                    const igraph_arpack_options_t *options,
568
0
                                    igraph_real_t *d, const igraph_real_t *v) {
569
570
0
    igraph_vector_t order;
571
0
    char sort[2];
572
0
    int apply = 1;
573
0
    unsigned int n = (unsigned int) options->n;
574
0
    int nconv = options->nconv;
575
0
    int nev = options->nev;
576
0
    unsigned int nans = (unsigned int) (nconv < nev ? nconv : nev);
577
0
    unsigned int i;
578
579
0
#define which(a,b) (options->which[0]==a && options->which[1]==b)
580
581
0
    if (which('L', 'A')) {
582
0
        sort[0] = 'S'; sort[1] = 'A';
583
0
    } else if (which('S', 'A')) {
584
0
        sort[0] = 'L'; sort[1] = 'A';
585
0
    } else if (which('L', 'M')) {
586
0
        sort[0] = 'S'; sort[1] = 'M';
587
0
    } else if (which('S', 'M')) {
588
0
        sort[0] = 'L'; sort[1] = 'M';
589
0
    } else if (which('B', 'E')) {
590
0
        sort[0] = 'L'; sort[1] = 'A';
591
0
    } else {
592
        /* None of the above, no sorting. These 'X' values are
593
         * ignored by ARPACK, but we set them anyway in order to
594
         * avoid an uninitialized 'sort' which would trigger
595
         * checkers such as MemorySanitizer. */
596
0
        sort[0] = 'X'; sort[1] = 'X';
597
0
    }
598
599
0
    IGRAPH_CHECK(igraph_vector_init_range(&order, 0, nconv));
600
0
    IGRAPH_FINALLY(igraph_vector_destroy, &order);
601
#ifdef HAVE_GFORTRAN
602
    igraphdsortr_(sort, &apply, &nconv, d, VECTOR(order), /*which_len=*/ 2);
603
#else
604
0
    igraphdsortr_(sort, &apply, &nconv, d, VECTOR(order));
605
0
#endif
606
607
    /* BE is special */
608
0
    if (which('B', 'E')) {
609
0
        int w = 0, l1 = 0, l2 = nev - 1;
610
0
        igraph_vector_t order2, d2;
611
0
        IGRAPH_VECTOR_INIT_FINALLY(&order2, nev);
612
0
        IGRAPH_VECTOR_INIT_FINALLY(&d2, nev);
613
0
        while (l1 <= l2) {
614
0
            VECTOR(order2)[w] = VECTOR(order)[l1];
615
0
            VECTOR(d2)[w] = d[l1];
616
0
            w++; l1++;
617
0
            if (l1 <= l2) {
618
0
                VECTOR(order2)[w] = VECTOR(order)[l2];
619
0
                VECTOR(d2)[w] = d[l2];
620
0
                w++; l2--;
621
0
            }
622
0
        }
623
0
        igraph_vector_update(&order, &order2);
624
0
        igraph_vector_copy_to(&d2, d);
625
0
        igraph_vector_destroy(&order2);
626
0
        igraph_vector_destroy(&d2);
627
0
        IGRAPH_FINALLY_CLEAN(2);
628
0
    }
629
630
0
#undef which
631
632
    /* Copy values */
633
0
    if (values) {
634
0
        IGRAPH_CHECK(igraph_vector_resize(values, nans));
635
0
        memcpy(VECTOR(*values), d, sizeof(igraph_real_t) * nans);
636
0
    }
637
638
    /* Reorder vectors */
639
0
    if (vectors) {
640
0
        IGRAPH_CHECK(igraph_matrix_resize(vectors, n, nans));
641
0
        for (i = 0; i < nans; i++) {
642
0
            unsigned int idx = (unsigned int) VECTOR(order)[i];
643
0
            const igraph_real_t *ptr = v + n * idx;
644
0
            memcpy(&MATRIX(*vectors, 0, i), ptr, sizeof(igraph_real_t) * n);
645
0
        }
646
0
    }
647
648
0
    igraph_vector_destroy(&order);
649
0
    IGRAPH_FINALLY_CLEAN(1);
650
651
0
    return IGRAPH_SUCCESS;
652
0
}
653
654
igraph_error_t igraph_arpack_rnsort(igraph_matrix_t *values, igraph_matrix_t *vectors,
655
                         const igraph_arpack_options_t *options,
656
                         igraph_real_t *dr, igraph_real_t *di,
657
0
                         igraph_real_t *v) {
658
659
0
    igraph_vector_t order;
660
0
    char sort[2];
661
0
    int apply = 1;
662
0
    unsigned int n = (unsigned int) options->n;
663
0
    int nconv = options->nconv;
664
0
    int nev = options->nev;
665
0
    unsigned int nans = (unsigned int) (nconv < nev ? nconv : nev);
666
0
    unsigned int i;
667
668
0
#define which(a,b) (options->which[0]==a && options->which[1]==b)
669
670
0
    if (which('L', 'M')) {
671
0
        sort[0] = 'S'; sort[1] = 'M';
672
0
    } else if (which('S', 'M')) {
673
0
        sort[0] = 'L'; sort[1] = 'M';
674
0
    } else if (which('L', 'R')) {
675
0
        sort[0] = 'S'; sort[1] = 'R';
676
0
    } else if (which('S', 'R')) {
677
0
        sort[0] = 'L'; sort[1] = 'R';
678
0
    } else if (which('L', 'I')) {
679
0
        sort[0] = 'S'; sort[1] = 'I';
680
0
    } else if (which('S', 'I')) {
681
0
        sort[0] = 'L'; sort[1] = 'I';
682
0
    } else {
683
        /* None of the above, no sorting. These 'X' values are
684
         * ignored by ARPACK, but we set them anyway in order to
685
         * avoid an uninitialized 'sort' which would trigger
686
         * checkers such as MemorySanitizer. */
687
0
        sort[0] = 'X'; sort[1] = 'X';
688
0
    }
689
690
0
#undef which
691
692
0
    IGRAPH_CHECK(igraph_vector_init_range(&order, 0, nconv));
693
0
    IGRAPH_FINALLY(igraph_vector_destroy, &order);
694
#ifdef HAVE_GFORTRAN
695
    igraphdsortc_(sort, &apply, &nconv, dr, di, VECTOR(order), /*which_len=*/ 2);
696
#else
697
0
    igraphdsortc_(sort, &apply, &nconv, dr, di, VECTOR(order));
698
0
#endif
699
700
0
    if (values) {
701
0
        IGRAPH_CHECK(igraph_matrix_resize(values, nans, 2));
702
0
        memcpy(&MATRIX(*values, 0, 0), dr, sizeof(igraph_real_t) * nans);
703
0
        memcpy(&MATRIX(*values, 0, 1), di, sizeof(igraph_real_t) * nans);
704
0
    }
705
706
0
    if (vectors) {
707
0
        int nc = 0, nr = 0, ncol, vx = 0;
708
0
        for (i = 0; i < nans; i++) {
709
0
            if (di[i] == 0) {
710
0
                nr++;
711
0
            } else {
712
0
                nc++;
713
0
            }
714
0
        }
715
0
        ncol = (nc / 2) * 2 + (nc % 2) * 2 + nr;
716
0
        IGRAPH_CHECK(igraph_matrix_resize(vectors, n, ncol));
717
718
0
        for (i = 0; i < nans; i++) {
719
0
            unsigned int idx;
720
721
0
            idx = (unsigned int) VECTOR(order)[i];
722
723
0
            if (di[i] == 0) {
724
                /* real eigenvalue, single eigenvector */
725
0
                memcpy(&MATRIX(*vectors, 0, vx), v + n * idx, sizeof(igraph_real_t) * n);
726
0
                vx++;
727
0
            } else if (di[i] > 0) {
728
                /* complex eigenvalue, positive imaginary part encountered first.
729
                 * ARPACK stores its eigenvector directly in two consecutive columns.
730
                 * The complex conjugate pair of the eigenvalue (if any) will be in
731
                 * the next column and we will skip it because we advance 'i' below */
732
0
                memcpy(&MATRIX(*vectors, 0, vx), v + n * idx, sizeof(igraph_real_t) * 2 * n);
733
0
                vx += 2;
734
0
                i++;
735
0
            } else {
736
                /* complex eigenvalue, negative imaginary part encountered first.
737
                     * The positive one will be the next one, but we need to copy the
738
                     * eigenvector corresponding to the eigenvalue with the positive
739
                     * imaginary part. */
740
0
                idx = (unsigned int) VECTOR(order)[i + 1];
741
0
                memcpy(&MATRIX(*vectors, 0, vx), v + n * idx, sizeof(igraph_real_t) * 2 * n);
742
0
                vx += 2;
743
0
                i++;
744
0
            }
745
0
        }
746
0
    }
747
748
0
    igraph_vector_destroy(&order);
749
0
    IGRAPH_FINALLY_CLEAN(1);
750
751
0
    if (values) {
752
        /* Strive to include complex conjugate eigenvalue pairs in a way that the
753
         * positive imaginary part comes first */
754
0
        for (i = 0; i < nans; i++) {
755
0
            if (MATRIX(*values, i, 1) == 0) {
756
                /* Real eigenvalue, nothing to do */
757
0
            } else if (MATRIX(*values, i, 1) < 0) {
758
                /* Negative imaginary part came first; negate the imaginary part for
759
                 * this eigenvalue and the next one (which is the complex conjugate
760
                 * pair), and skip it */
761
0
                MATRIX(*values, i, 1) *= -1;
762
0
                i++;
763
0
                if (i < nans) {
764
0
                    MATRIX(*values, i, 1) *= -1;
765
0
                }
766
0
            } else {
767
                /* Positive imaginary part; skip the next eigenvalue, which is the
768
                 * complex conjugate pair */
769
0
                i++;
770
0
            }
771
0
        }
772
0
    }
773
774
0
    return IGRAPH_SUCCESS;
775
0
}
776
777
/**
778
 * \function igraph_i_arpack_auto_ncv
779
 * \brief Tries to set up the value of \c ncv in an \c igraph_arpack_options_t
780
 *        automagically.
781
 */
782
0
static void igraph_i_arpack_auto_ncv(igraph_arpack_options_t* options) {
783
    /* This is similar to how Octave determines the value of ncv, with some
784
     * modifications. */
785
0
    int min_ncv = options->nev * 2 + 1;
786
787
    /* Use twice the number of desired eigenvectors plus one by default */
788
0
    options->ncv = min_ncv;
789
    /* ...but use at least 20 Lanczos vectors... */
790
0
    if (options->ncv < 20) {
791
0
        options->ncv = 20;
792
0
    }
793
    /* ...but having ncv close to n leads to some problems with small graphs
794
     * (example: PageRank of "A <--> C, D <--> E, B"), so we try to keep it
795
     * no more than min(n/2 + 2, n - 1), bounds found empirically using the
796
     * eigen_stress.c test...
797
     */
798
0
    if (options->ncv > options->n / 2 + 2) {
799
0
        options->ncv = options->n / 2 + 2;
800
0
    }
801
0
    if (options->ncv > options->n - 1) {
802
0
        options->ncv = options->n - 1;
803
0
    }
804
    /* ...but we need at least min_ncv. */
805
0
    if (options->ncv < min_ncv) {
806
0
        options->ncv = min_ncv;
807
0
    }
808
    /* ...but at most n */
809
0
    if (options->ncv > options->n) {
810
0
        options->ncv = options->n;
811
0
    }
812
0
}
813
814
/**
815
 * \function igraph_i_arpack_report_no_convergence
816
 * \brief Prints a warning that informs the user that the ARPACK solver
817
 *        did not converge.
818
 */
819
0
static void igraph_i_arpack_report_no_convergence(const igraph_arpack_options_t* options) {
820
0
    char buf[1024];
821
0
    snprintf(buf, sizeof(buf), "ARPACK solver failed to converge (%d iterations, "
822
0
             "%d/%d eigenvectors converged)", options->iparam[2],
823
0
             options->iparam[4], options->nev);
824
0
    IGRAPH_WARNING(buf);
825
0
}
826
827
/**
828
 * \function igraph_arpack_rssolve
829
 * \brief ARPACK solver for symmetric matrices.
830
 *
831
 * This is the ARPACK solver for symmetric matrices. Please use
832
 * \ref igraph_arpack_rnsolve() for non-symmetric matrices.
833
 * \param fun Pointer to an \ref igraph_arpack_function_t object,
834
 *     the function that performs the matrix-vector multiplication.
835
 * \param extra An extra argument to be passed to \c fun.
836
 * \param options An \ref igraph_arpack_options_t object.
837
 * \param storage An \ref igraph_arpack_storage_t object, or a null
838
 *     pointer. In the latter case memory allocation and deallocation
839
 *     is performed automatically. Either this or the \p vectors argument
840
 *     must be non-null if the ARPACK iteration is started from a
841
 *     given starting vector. If both are given \p vectors take
842
 *     precedence.
843
 * \param values If not a null pointer, then it should be a pointer to an
844
 *     initialized vector. The eigenvalues will be stored here. The
845
 *     vector will be resized as needed.
846
 * \param vectors If not a null pointer, then it must be a pointer to
847
 *     an initialized matrix. The eigenvectors will be stored in the
848
 *     columns of the matrix. The matrix will be resized as needed.
849
 *     Either this or the \p storage argument must be non-null if the
850
 *     ARPACK iteration is started from a given starting vector. If
851
 *     both are given \p vectors take precedence.
852
 * \return Error code.
853
 *
854
 * Time complexity: depends on the matrix-vector
855
 * multiplication. Usually a small number of iterations is enough, so
856
 * if the matrix is sparse and the matrix-vector multiplication can be
857
 * done in O(n) time (the number of vertices), then the eigenvalues
858
 * are found in O(n) time as well.
859
 */
860
861
igraph_error_t igraph_arpack_rssolve(igraph_arpack_function_t *fun, void *extra,
862
                          igraph_arpack_options_t *options,
863
                          igraph_arpack_storage_t *storage,
864
0
                          igraph_vector_t *values, igraph_matrix_t *vectors) {
865
866
0
    igraph_real_t *v, *workl, *workd, *d, *resid, *ax;
867
0
    igraph_bool_t free_them = false;
868
0
    int *select, i;
869
870
0
    int ido = 0;
871
0
    int rvec = vectors || storage ? 1 : 0; /* calculate eigenvectors? */
872
0
    char *all = "A";
873
874
0
    int origldv = options->ldv, origlworkl = options->lworkl,
875
0
        orignev = options->nev, origncv = options->ncv;
876
0
    igraph_real_t origtol = options->tol;
877
0
    char origwhich[2];
878
879
0
    origwhich[0] = options->which[0];
880
0
    origwhich[1] = options->which[1];
881
882
    /* Special case for 1x1 and 2x2 matrices in mode 1 */
883
0
    if (options->mode == 1 && options->n == 1) {
884
0
        return igraph_i_arpack_rssolve_1x1(fun, extra, options, values, vectors);
885
0
    } else if (options->mode == 1 && options->n == 2) {
886
0
        return igraph_i_arpack_rssolve_2x2(fun, extra, options, values, vectors);
887
0
    }
888
889
    /* Brush up options if needed */
890
0
    if (options->ldv == 0) {
891
0
        options->ldv = options->n;
892
0
    }
893
0
    if (options->ncv == 0) {
894
0
        igraph_i_arpack_auto_ncv(options);
895
0
    }
896
0
    if (options->lworkl == 0) {
897
0
        options->lworkl = options->ncv * (options->ncv + 8);
898
0
    }
899
0
    if (options->which[0] == 'X') {
900
0
        options->which[0] = 'L';
901
0
        options->which[1] = 'M';
902
0
    }
903
904
0
    if (storage) {
905
        /* Storage provided */
906
0
        if (storage->maxn < options->n) {
907
0
            IGRAPH_ERROR("Not enough storage for ARPACK (`n')", IGRAPH_EINVAL);
908
0
        }
909
0
        if (storage->maxncv < options->ncv) {
910
0
            IGRAPH_ERROR("Not enough storage for ARPACK (`ncv')", IGRAPH_EINVAL);
911
0
        }
912
0
        if (storage->maxldv < options->ldv) {
913
0
            IGRAPH_ERROR("Not enough storage for ARPACK (`ldv')", IGRAPH_EINVAL);
914
0
        }
915
916
0
        v      = storage->v;
917
0
        workl  = storage->workl;
918
0
        workd  = storage->workd;
919
0
        d      = storage->d;
920
0
        resid  = storage->resid;
921
0
        ax     = storage->ax;
922
0
        select = storage->select;
923
924
0
    } else {
925
        /* Storage not provided */
926
0
        free_them = true;
927
928
0
#define CHECKMEM(x) \
929
0
    if (!x) { \
930
0
        IGRAPH_ERROR("Cannot allocate memory for ARPACK", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ \
931
0
    } \
932
0
    IGRAPH_FINALLY(igraph_free, x);
933
934
0
        v = IGRAPH_CALLOC(options->ldv * options->ncv, igraph_real_t); CHECKMEM(v);
935
0
        workl = IGRAPH_CALLOC(options->lworkl, igraph_real_t); CHECKMEM(workl);
936
0
        workd = IGRAPH_CALLOC(3 * options->n, igraph_real_t); CHECKMEM(workd);
937
0
        d = IGRAPH_CALLOC(2 * options->ncv, igraph_real_t); CHECKMEM(d);
938
0
        resid = IGRAPH_CALLOC(options->n, igraph_real_t); CHECKMEM(resid);
939
0
        ax = IGRAPH_CALLOC(options->n, igraph_real_t); CHECKMEM(ax);
940
0
        select = IGRAPH_CALLOC(options->ncv, int); CHECKMEM(select);
941
942
0
#undef CHECKMEM
943
944
0
    }
945
946
    /* Set final bits */
947
0
    options->bmat[0] = 'I';
948
0
    options->iparam[0] = options->ishift;
949
0
    options->iparam[1] = 0;   // not referenced
950
0
    options->iparam[2] = options->mxiter;
951
0
    options->iparam[3] = 1;   // currently dsaupd() works only for nb=1
952
0
    options->iparam[4] = 0;
953
0
    options->iparam[5] = 0;   // not referenced
954
0
    options->iparam[6] = options->mode;
955
0
    options->iparam[7] = 0;   // return value
956
0
    options->iparam[8] = 0;   // return value
957
0
    options->iparam[9] = 0;   // return value
958
0
    options->iparam[10] = 0;  // return value
959
0
    options->info = 1;  // always use a provided starting vector
960
0
    if (options->start) {
961
        // user provided the starting vector so we just use that
962
0
        if (!storage && !vectors) {
963
0
            IGRAPH_ERROR("Starting vector not given", IGRAPH_EINVAL);
964
0
        }
965
0
        if (vectors && (igraph_matrix_nrow(vectors) != options->n ||
966
0
                        igraph_matrix_ncol(vectors) < 1)) {
967
0
            IGRAPH_ERROR("Invalid starting vector size", IGRAPH_EINVAL);
968
0
        }
969
0
        if (vectors) {
970
0
            for (i = 0; i < options->n; i++) {
971
0
                resid[i] = MATRIX(*vectors, i, 0);
972
0
            }
973
0
        }
974
0
    } else {
975
        // we need to generate a random vector on our own; let's not rely on
976
        // ARPACK to do so because we want to use our own RNG
977
0
        for (i = 0; i < options->n; i++) {
978
0
            resid[i] = RNG_UNIF(-1, 1);
979
0
        }
980
0
    }
981
982
    /* Ok, we have everything */
983
0
    while (1) {
984
0
        igraph_real_t *from, *to;
985
986
0
        IGRAPH_ALLOW_INTERRUPTION();
987
988
#ifdef HAVE_GFORTRAN
989
        igraphdsaupd_(&ido, options->bmat, &options->n, options->which,
990
                      &options->nev, &options->tol,
991
                      resid, &options->ncv, v, &options->ldv,
992
                      options->iparam, options->ipntr,
993
                      workd, workl, &options->lworkl, &options->info,
994
                      /*bmat_len=*/ 1, /*which_len=*/ 2);
995
#else
996
0
        igraphdsaupd_(&ido, options->bmat, &options->n, options->which,
997
0
                      &options->nev, &options->tol,
998
0
                      resid, &options->ncv, v, &options->ldv,
999
0
                      options->iparam, options->ipntr,
1000
0
                      workd, workl, &options->lworkl, &options->info);
1001
0
#endif
1002
        /* When there is a non-zero error code in options->info, we expect that
1003
         * ARPACK requests a termination of the iteration by setting ido=99. */
1004
0
        IGRAPH_ASSERT(ido == 99 || options->info == 0);
1005
1006
0
        if (ido == -1 || ido == 1) {
1007
0
            from = workd + options->ipntr[0] - 1;
1008
0
            to = workd + options->ipntr[1] - 1;
1009
0
            IGRAPH_CHECK(fun(to, from, options->n, extra));
1010
0
        } else if (ido == 2) {
1011
0
            from = workd + options->ipntr[0] - 1;
1012
0
            to = workd + options->ipntr[1] - 1;
1013
0
            memcpy(to, from, sizeof(igraph_real_t) * options->n);
1014
0
        } else if (ido == 99) {
1015
0
            break;
1016
0
        } else {
1017
0
            IGRAPH_ERRORF("Unexpected IDO value %d when running ARPACK.", IGRAPH_FAILURE, ido);
1018
0
        }
1019
0
    }
1020
1021
0
    if (options->info == 1) {
1022
0
        igraph_i_arpack_report_no_convergence(options);
1023
0
    }
1024
0
    if (options->info != 0) {
1025
0
        IGRAPH_ARPACK_ERROR(igraph_i_arpack_err_dsaupd(options->info));
1026
0
    }
1027
1028
0
    options->ierr = 0;
1029
#ifdef HAVE_GFORTRAN
1030
    igraphdseupd_(&rvec, all, select, d, v, &options->ldv,
1031
                  &options->sigma, options->bmat, &options->n,
1032
                  options->which, &options->nev, &options->tol,
1033
                  resid, &options->ncv, v, &options->ldv, options->iparam,
1034
                  options->ipntr, workd, workl, &options->lworkl,
1035
                  &options->ierr, /*howmny_len=*/ 1, /*bmat_len=*/ 1,
1036
                  /*which_len=*/ 2);
1037
#else
1038
0
    igraphdseupd_(&rvec, all, select, d, v, &options->ldv,
1039
0
                  &options->sigma, options->bmat, &options->n,
1040
0
                  options->which, &options->nev, &options->tol,
1041
0
                  resid, &options->ncv, v, &options->ldv, options->iparam,
1042
0
                  options->ipntr, workd, workl, &options->lworkl,
1043
0
                  &options->ierr);
1044
0
#endif
1045
1046
0
    if (options->ierr != 0) {
1047
0
        IGRAPH_ARPACK_ERROR(igraph_i_arpack_err_dseupd(options->ierr));
1048
0
    }
1049
1050
    /* Save the result */
1051
1052
0
    options->noiter = options->iparam[2];
1053
0
    options->nconv = options->iparam[4];
1054
0
    options->numop = options->iparam[8];
1055
0
    options->numopb = options->iparam[9];
1056
0
    options->numreo = options->iparam[10];
1057
1058
0
    if (options->nconv < options->nev) {
1059
0
        IGRAPH_WARNING("Not enough eigenvalues/vectors in symmetric ARPACK "
1060
0
                       "solver");
1061
0
    }
1062
1063
0
    if (values || vectors) {
1064
0
        IGRAPH_CHECK(igraph_arpack_rssort(values, vectors, options, d, v));
1065
0
    }
1066
1067
0
    options->ldv = origldv;
1068
0
    options->ncv = origncv;
1069
0
    options->lworkl = origlworkl;
1070
0
    options->which[0] = origwhich[0]; options->which[1] = origwhich[1];
1071
0
    options->tol = origtol;
1072
0
    options->nev = orignev;
1073
1074
    /* Clean up if needed */
1075
0
    if (free_them) {
1076
0
        IGRAPH_FREE(select);
1077
0
        IGRAPH_FREE(ax);
1078
0
        IGRAPH_FREE(resid);
1079
0
        IGRAPH_FREE(d);
1080
0
        IGRAPH_FREE(workd);
1081
0
        IGRAPH_FREE(workl);
1082
0
        IGRAPH_FREE(v);
1083
0
        IGRAPH_FINALLY_CLEAN(7);
1084
0
    }
1085
0
    return IGRAPH_SUCCESS;
1086
0
}
1087
1088
/**
1089
 * \function igraph_arpack_rnsolve
1090
 * \brief ARPACK solver for non-symmetric matrices.
1091
 *
1092
 * Please always consider calling \ref igraph_arpack_rssolve() if your
1093
 * matrix is symmetric, it is much faster.
1094
 * \ref igraph_arpack_rnsolve() for non-symmetric matrices.
1095
 * </para><para>
1096
 * Note that ARPACK is not called for 2x2 matrices as an exact algebraic
1097
 * solution exists in these cases.
1098
 *
1099
 * \param fun Pointer to an \ref igraph_arpack_function_t object,
1100
 *     the function that performs the matrix-vector multiplication.
1101
 * \param extra An extra argument to be passed to \c fun.
1102
 * \param options An \ref igraph_arpack_options_t object.
1103
 * \param storage An \ref igraph_arpack_storage_t object, or a null
1104
 *     pointer. In the latter case memory allocation and deallocation
1105
 *     is performed automatically.
1106
 * \param values If not a null pointer, then it should be a pointer to an
1107
 *     initialized matrix. The (possibly complex) eigenvalues will be
1108
 *     stored here. The matrix will have two columns, the first column
1109
 *     contains the real, the second the imaginary parts of the
1110
 *     eigenvalues.
1111
 *     The matrix will be resized as needed.
1112
 * \param vectors If not a null pointer, then it must be a pointer to
1113
 *     an initialized matrix. The eigenvectors will be stored in the
1114
 *     columns of the matrix. The matrix will be resized as needed.
1115
 *     Note that real eigenvalues will have real eigenvectors in a single
1116
 *     column in this matrix; however, complex eigenvalues come in conjugate
1117
 *     pairs and the result matrix will store the eigenvector corresponding to
1118
 *     the eigenvalue with \em positive imaginary part only. Since in this case
1119
 *     the eigenvector is also complex, it will occupy \em two columns in the
1120
 *     eigenvector matrix (the real and the imaginary parts, in this order).
1121
 *     Caveat: if the eigenvalue vector returns only the eigenvalue with the
1122
 *     \em negative imaginary part for a complex conjugate eigenvalue pair, the
1123
 *     result vector will \em still store the eigenvector corresponding to the
1124
 *     eigenvalue with the positive imaginary part (since this is how ARPACK
1125
 *     works).
1126
 * \return Error code.
1127
 *
1128
 * Time complexity: depends on the matrix-vector
1129
 * multiplication. Usually a small number of iterations is enough, so
1130
 * if the matrix is sparse and the matrix-vector multiplication can be
1131
 * done in O(n) time (the number of vertices), then the eigenvalues
1132
 * are found in O(n) time as well.
1133
 */
1134
1135
igraph_error_t igraph_arpack_rnsolve(igraph_arpack_function_t *fun, void *extra,
1136
                          igraph_arpack_options_t *options,
1137
                          igraph_arpack_storage_t *storage,
1138
0
                          igraph_matrix_t *values, igraph_matrix_t *vectors) {
1139
1140
0
    igraph_real_t *v, *workl, *workd, *dr, *di, *resid, *workev;
1141
0
    igraph_bool_t free_them = false;
1142
0
    int *select, i;
1143
1144
0
    int ido = 0;
1145
0
    int rvec = vectors || storage ? 1 : 0;
1146
0
    char *all = "A";
1147
1148
0
    int origldv = options->ldv, origlworkl = options->lworkl,
1149
0
        orignev = options->nev, origncv = options->ncv;
1150
0
    igraph_real_t origtol = options->tol;
1151
0
    int d_size;
1152
0
    char origwhich[2];
1153
1154
0
    origwhich[0] = options->which[0];
1155
0
    origwhich[1] = options->which[1];
1156
1157
    /* Special case for 1x1 and 2x2 matrices in mode 1 */
1158
0
    if (options->mode == 1 && options->n == 1) {
1159
0
        return igraph_i_arpack_rnsolve_1x1(fun, extra, options, values, vectors);
1160
0
    } else if (options->mode == 1 && options->n == 2) {
1161
0
        return igraph_i_arpack_rnsolve_2x2(fun, extra, options, values, vectors);
1162
0
    }
1163
1164
    /* Brush up options if needed */
1165
0
    if (options->ldv == 0) {
1166
0
        options->ldv = options->n;
1167
0
    }
1168
0
    if (options->ncv == 0) {
1169
0
        igraph_i_arpack_auto_ncv(options);
1170
0
    }
1171
0
    if (options->lworkl == 0) {
1172
0
        options->lworkl = 3 * options->ncv * (options->ncv + 2);
1173
0
    }
1174
0
    if (options->which[0] == 'X') {
1175
0
        options->which[0] = 'L';
1176
0
        options->which[1] = 'M';
1177
0
    }
1178
1179
0
    if (storage) {
1180
        /* Storage provided */
1181
0
        if (storage->maxn < options->n) {
1182
0
            IGRAPH_ERROR("Not enough storage for ARPACK (`n')", IGRAPH_EINVAL);
1183
0
        }
1184
0
        if (storage->maxncv < options->ncv) {
1185
0
            IGRAPH_ERROR("Not enough storage for ARPACK (`ncv')", IGRAPH_EINVAL);
1186
0
        }
1187
0
        if (storage->maxldv < options->ldv) {
1188
0
            IGRAPH_ERROR("Not enough storage for ARPACK (`ldv')", IGRAPH_EINVAL);
1189
0
        }
1190
1191
0
        v      = storage->v;
1192
0
        workl  = storage->workl;
1193
0
        workd  = storage->workd;
1194
0
        workev = storage->workev;
1195
0
        dr     = storage->d;
1196
0
        di     = storage->di;
1197
0
        d_size = options->n;
1198
0
        resid  = storage->resid;
1199
0
        select = storage->select;
1200
1201
0
    } else {
1202
        /* Storage not provided */
1203
0
        free_them = true;
1204
1205
0
#define CHECKMEM(x) \
1206
0
    if (!x) { \
1207
0
        IGRAPH_ERROR("Cannot allocate memory for ARPACK", IGRAPH_ENOMEM); /* LCOV_EXCL_LINE */ \
1208
0
    } \
1209
0
    IGRAPH_FINALLY(igraph_free, x);
1210
1211
0
        v = IGRAPH_CALLOC(options->n * options->ncv, igraph_real_t); CHECKMEM(v);
1212
0
        workl = IGRAPH_CALLOC(options->lworkl, igraph_real_t); CHECKMEM(workl);
1213
0
        workd = IGRAPH_CALLOC(3 * options->n, igraph_real_t); CHECKMEM(workd);
1214
0
        d_size = 2 * options->nev + 1 > options->ncv ? 2 * options->nev + 1 : options->ncv;
1215
0
        dr = IGRAPH_CALLOC(d_size, igraph_real_t); CHECKMEM(dr);
1216
0
        di = IGRAPH_CALLOC(d_size, igraph_real_t); CHECKMEM(di);
1217
0
        resid = IGRAPH_CALLOC(options->n, igraph_real_t); CHECKMEM(resid);
1218
0
        select = IGRAPH_CALLOC(options->ncv, int); CHECKMEM(select);
1219
0
        workev = IGRAPH_CALLOC(3 * options->ncv, igraph_real_t); CHECKMEM(workev);
1220
1221
0
#undef CHECKMEM
1222
1223
0
    }
1224
1225
    /* Set final bits */
1226
0
    options->bmat[0] = 'I';
1227
0
    options->iparam[0] = options->ishift;
1228
0
    options->iparam[1] = 0;   // not referenced
1229
0
    options->iparam[2] = options->mxiter;
1230
0
    options->iparam[3] = 1;   // currently dnaupd() works only for nb=1
1231
0
    options->iparam[4] = 0;
1232
0
    options->iparam[5] = 0;   // not referenced
1233
0
    options->iparam[6] = options->mode;
1234
0
    options->iparam[7] = 0;   // return value
1235
0
    options->iparam[8] = 0;   // return value
1236
0
    options->iparam[9] = 0;   // return value
1237
0
    options->iparam[10] = 0;  // return value
1238
0
    options->info = 1;  // always use a provided starting vector
1239
0
    if (options->start) {
1240
0
        if (!storage && !vectors) {
1241
0
            IGRAPH_ERROR("Starting vector not given", IGRAPH_EINVAL);
1242
0
        }
1243
0
        if (vectors && (igraph_matrix_nrow(vectors) != options->n ||
1244
0
                        igraph_matrix_ncol(vectors) != 1)) {
1245
0
            IGRAPH_ERROR("Invalid starting vector size", IGRAPH_EINVAL);
1246
0
        }
1247
0
        if (vectors) {
1248
0
            for (i = 0; i < options->n; i++) {
1249
0
                resid[i] = MATRIX(*vectors, i, 0);
1250
0
            }
1251
0
        }
1252
0
    } else {
1253
        // we need to generate a random vector on our own; let's not rely on
1254
        // ARPACK to do so because we want to use our own RNG
1255
0
        for (i = 0; i < options->n; i++) {
1256
0
            resid[i] = RNG_UNIF(-1, 1);
1257
0
        }
1258
0
    }
1259
1260
    /* Ok, we have everything */
1261
0
    while (1) {
1262
0
        igraph_real_t *from, *to;
1263
1264
0
        IGRAPH_ALLOW_INTERRUPTION();
1265
1266
#ifdef HAVE_GFORTRAN
1267
        igraphdnaupd_(&ido, options->bmat, &options->n, options->which,
1268
                      &options->nev, &options->tol,
1269
                      resid, &options->ncv, v, &options->ldv,
1270
                      options->iparam, options->ipntr,
1271
                      workd, workl, &options->lworkl, &options->info,
1272
                      /*bmat_len=*/ 1, /*which_len=*/ 2);
1273
#else
1274
0
        igraphdnaupd_(&ido, options->bmat, &options->n, options->which,
1275
0
                      &options->nev, &options->tol,
1276
0
                      resid, &options->ncv, v, &options->ldv,
1277
0
                      options->iparam, options->ipntr,
1278
0
                      workd, workl, &options->lworkl, &options->info);
1279
0
#endif
1280
        /* When there is a non-zero error code in options->info, we expect that
1281
         * ARPACK requests a termination of the iteration by setting ido=99. */
1282
0
        IGRAPH_ASSERT(ido == 99 || options->info == 0);
1283
1284
0
        if (ido == -1 || ido == 1) {
1285
0
            from = workd + options->ipntr[0] - 1;
1286
0
            to = workd + options->ipntr[1] - 1;
1287
0
            IGRAPH_CHECK(fun(to, from, options->n, extra));
1288
0
        } else if (ido == 2) {
1289
0
            from = workd + options->ipntr[0] - 1;
1290
0
            to = workd + options->ipntr[1] - 1;
1291
0
            memcpy(to, from, sizeof(igraph_real_t) * options->n);
1292
0
        } else if (ido == 4) {
1293
            /* same as ido == 1 but the arguments are at different places */
1294
0
            from = workd + options->ipntr[0] - 1;
1295
0
            to = workd + options->ipntr[2] - 1;
1296
0
            IGRAPH_CHECK(fun(to, from, options->n, extra));
1297
0
        } else if (ido == 99) {
1298
0
            break;
1299
0
        } else {
1300
0
            IGRAPH_ERRORF("Unexpected IDO value %d when running ARPACK.", IGRAPH_FAILURE, ido);
1301
0
        }
1302
0
    }
1303
1304
0
    if (options->info == 1) {
1305
0
        igraph_i_arpack_report_no_convergence(options);
1306
0
    }
1307
0
    if (options->info != 0 && options->info != -9999) {
1308
0
        IGRAPH_ARPACK_ERROR(igraph_i_arpack_err_dnaupd(options->info));
1309
0
    }
1310
1311
0
    options->ierr = 0;
1312
#ifdef HAVE_GFORTRAN
1313
    igraphdneupd_(&rvec, all, select, dr, di, v, &options->ldv,
1314
                  &options->sigma, &options->sigmai, workev, options->bmat,
1315
                  &options->n, options->which, &options->nev, &options->tol,
1316
                  resid, &options->ncv, v, &options->ldv, options->iparam,
1317
                  options->ipntr, workd, workl, &options->lworkl,
1318
                  &options->ierr, /*howmny_len=*/ 1, /*bmat_len=*/ 1,
1319
                  /*which_len=*/ 2);
1320
#else
1321
0
    igraphdneupd_(&rvec, all, select, dr, di, v, &options->ldv,
1322
0
                  &options->sigma, &options->sigmai, workev, options->bmat,
1323
0
                  &options->n, options->which, &options->nev, &options->tol,
1324
0
                  resid, &options->ncv, v, &options->ldv, options->iparam,
1325
0
                  options->ipntr, workd, workl, &options->lworkl,
1326
0
                  &options->ierr);
1327
0
#endif
1328
1329
0
    if (options->ierr != 0) {
1330
0
        IGRAPH_ARPACK_ERROR(igraph_i_arpack_err_dneupd(options->ierr));
1331
0
    }
1332
1333
    /* Save the result */
1334
1335
0
    options->noiter = options->iparam[2];
1336
0
    options->nconv = options->iparam[4];
1337
0
    options->numop = options->iparam[8];
1338
0
    options->numopb = options->iparam[9];
1339
0
    options->numreo = options->iparam[10];
1340
1341
0
    if (options->nconv < options->nev) {
1342
0
        IGRAPH_WARNING("Not enough eigenvalues/vectors in ARPACK "
1343
0
                       "solver");
1344
0
    }
1345
1346
    /* ARPACK might modify stuff in 'options' so reset everything that could
1347
     * potentially get modified */
1348
0
    options->ldv = origldv;
1349
0
    options->ncv = origncv;
1350
0
    options->lworkl = origlworkl;
1351
0
    options->which[0] = origwhich[0]; options->which[1] = origwhich[1];
1352
0
    options->tol = origtol;
1353
0
    options->nev = orignev;
1354
1355
0
    if (values || vectors) {
1356
0
        IGRAPH_CHECK(igraph_arpack_rnsort(values, vectors, options,
1357
0
                                          dr, di, v));
1358
0
    }
1359
1360
    /* Clean up if needed */
1361
0
    if (free_them) {
1362
0
        IGRAPH_FREE(workev);
1363
0
        IGRAPH_FREE(select);
1364
0
        IGRAPH_FREE(resid);
1365
0
        IGRAPH_FREE(di);
1366
0
        IGRAPH_FREE(dr);
1367
0
        IGRAPH_FREE(workd);
1368
0
        IGRAPH_FREE(workl);
1369
0
        IGRAPH_FREE(v);
1370
0
        IGRAPH_FINALLY_CLEAN(8);
1371
0
    }
1372
0
    return IGRAPH_SUCCESS;
1373
0
}
1374
1375
/**
1376
 * \function igraph_arpack_unpack_complex
1377
 * \brief Makes the result of the non-symmetric ARPACK solver more readable.
1378
 *
1379
 * This function works on the output of \ref igraph_arpack_rnsolve and
1380
 * brushes it up a bit: it only keeps \p nev eigenvalues/vectors and
1381
 * every eigenvector is stored in two columns of the \p vectors
1382
 * matrix.
1383
 *
1384
 * </para><para>
1385
 * The output of the non-symmetric ARPACK solver is somewhat hard to
1386
 * parse, as real eigenvectors occupy only one column in the matrix,
1387
 * and the complex conjugate eigenvectors are not stored at all
1388
 * (usually). The other problem is that the solver might return more
1389
 * eigenvalues than requested. The common use of this function is to
1390
 * call it directly after \ref igraph_arpack_rnsolve with its \p
1391
 * vectors and \p values argument and \c options->nev as \p nev.
1392
 * This will add the vectors for eigenvalues with a negative imaginary
1393
 * part and return all vectors as 2 columns, a real and imaginary part.
1394
 * \param vectors The eigenvector matrix, as returned by \ref
1395
 *   igraph_arpack_rnsolve. It will be resized, typically it will be
1396
 *   larger.
1397
 * \param values The eigenvalue matrix, as returned by \ref
1398
 *   igraph_arpack_rnsolve. It will be resized, typically extra,
1399
 *   unneeded rows (=eigenvalues) will be removed.
1400
 * \param nev The number of eigenvalues/vectors to keep. Can be less
1401
 *   or equal than the number originally requested from ARPACK.
1402
 * \return Error code.
1403
 *
1404
 * Time complexity: linear in the number of elements in the \p vectors
1405
 * matrix.
1406
 */
1407
1408
igraph_error_t igraph_arpack_unpack_complex(igraph_matrix_t *vectors, igraph_matrix_t *values,
1409
0
                                 igraph_int_t nev) {
1410
1411
0
    igraph_int_t nodes = igraph_matrix_nrow(vectors);
1412
0
    igraph_int_t no_evs = igraph_matrix_nrow(values);
1413
0
    igraph_int_t i, j;
1414
0
    igraph_int_t new_vector_pos, vector_pos;
1415
0
    igraph_matrix_t new_vectors;
1416
1417
    /* Error checks */
1418
0
    if (nev < 0) {
1419
0
        IGRAPH_ERROR("`nev' cannot be negative.", IGRAPH_EINVAL);
1420
0
    }
1421
0
    if (nev > no_evs) {
1422
0
        IGRAPH_ERROR("`nev' too large, we don't have that many in `values'.",
1423
0
                     IGRAPH_EINVAL);
1424
0
    }
1425
1426
0
    for (i = no_evs -1; i >= nev; i--) {
1427
0
        IGRAPH_CHECK(igraph_matrix_remove_row(values, i));
1428
0
    }
1429
1430
0
    IGRAPH_CHECK(igraph_matrix_init(&new_vectors, nodes, nev * 2));
1431
0
    IGRAPH_FINALLY(igraph_matrix_destroy, &new_vectors);
1432
1433
0
    new_vector_pos = 0;
1434
0
    vector_pos = 0;
1435
0
    for (i = 0; i < nev && vector_pos < igraph_matrix_ncol(vectors); i++) {
1436
0
        if (MATRIX(*values, i, 1) == 0) {
1437
            /* Real eigenvalue */
1438
0
            for (j = 0; j < nodes; j++) {
1439
0
                MATRIX(new_vectors, j, new_vector_pos) = MATRIX(*vectors, j, vector_pos);
1440
0
            }
1441
0
            new_vector_pos += 2;
1442
0
            vector_pos += 1;
1443
0
        } else {
1444
            /* complex eigenvalue */
1445
0
            for (j = 0; j < nodes; j++) {
1446
0
                MATRIX(new_vectors, j, new_vector_pos) = MATRIX(*vectors, j, vector_pos);
1447
0
                MATRIX(new_vectors, j, new_vector_pos + 1) = MATRIX(*vectors, j, vector_pos + 1);
1448
0
            }
1449
1450
            /* handle the conjugate */
1451
1452
            /* first check if the conjugate eigenvalue is there */
1453
0
            i++;
1454
0
            if (i >= nev) {
1455
0
                break;
1456
0
            }
1457
1458
0
            if (MATRIX(*values, i, 1) != -MATRIX(*values, i-1, 1)) {
1459
0
                IGRAPH_ERROR("Complex eigenvalue not followed by its conjugate.", IGRAPH_EINVAL);
1460
0
            }
1461
1462
            /* then copy and negate */
1463
0
            for (j = 0; j < nodes; j++) {
1464
0
                MATRIX(new_vectors, j, new_vector_pos + 2) = MATRIX(*vectors, j, vector_pos);
1465
0
                MATRIX(new_vectors, j, new_vector_pos + 3) = -MATRIX(*vectors, j, vector_pos + 1);
1466
0
            }
1467
0
            new_vector_pos += 4;
1468
0
            vector_pos += 2;
1469
0
        }
1470
0
    }
1471
0
    igraph_matrix_destroy(vectors);
1472
0
    IGRAPH_CHECK(igraph_matrix_init_copy(vectors, &new_vectors));
1473
0
    igraph_matrix_destroy(&new_vectors);
1474
0
    IGRAPH_FINALLY_CLEAN(1);
1475
1476
0
    return IGRAPH_SUCCESS;
1477
0
}
1478
1479
/* ************************************************************************* */
1480
1481
/* Helper functions to map ARPACK error codes to \c igraph_arpack_error_t    */
1482
1483
0
static igraph_arpack_error_t igraph_i_arpack_err_dsaupd(int error) {
1484
0
    switch (error) {
1485
0
    case  0:      return IGRAPH_ARPACK_NO_ERROR;
1486
0
    case  1:      return IGRAPH_ARPACK_MAXIT;
1487
0
    case  3:      return IGRAPH_ARPACK_NOSHIFT;
1488
0
    case -1:      return IGRAPH_ARPACK_NPOS;
1489
0
    case -2:      return IGRAPH_ARPACK_NEVNPOS;
1490
0
    case -3:      return IGRAPH_ARPACK_NCVSMALL;
1491
0
    case -4:      return IGRAPH_ARPACK_NONPOSI;
1492
0
    case -5:      return IGRAPH_ARPACK_WHICHINV;
1493
0
    case -6:      return IGRAPH_ARPACK_BMATINV;
1494
0
    case -7:      return IGRAPH_ARPACK_WORKLSMALL;
1495
0
    case -8:      return IGRAPH_ARPACK_TRIDERR;
1496
0
    case -9:      return IGRAPH_ARPACK_ZEROSTART;
1497
0
    case -10:     return IGRAPH_ARPACK_MODEINV;
1498
0
    case -11:     return IGRAPH_ARPACK_MODEBMAT;
1499
0
    case -12:     return IGRAPH_ARPACK_ISHIFT;
1500
0
    case -13:     return IGRAPH_ARPACK_NEVBE;
1501
0
    case -9999:   return IGRAPH_ARPACK_NOFACT;
1502
0
    default:      return IGRAPH_ARPACK_UNKNOWN;
1503
0
    }
1504
0
}
1505
1506
0
static igraph_arpack_error_t igraph_i_arpack_err_dseupd(int error) {
1507
0
    switch (error) {
1508
0
    case  0:      return IGRAPH_ARPACK_NO_ERROR;
1509
0
    case -1:      return IGRAPH_ARPACK_NPOS;
1510
0
    case -2:      return IGRAPH_ARPACK_NEVNPOS;
1511
0
    case -3:      return IGRAPH_ARPACK_NCVSMALL;
1512
0
    case -5:      return IGRAPH_ARPACK_WHICHINV;
1513
0
    case -6:      return IGRAPH_ARPACK_BMATINV;
1514
0
    case -7:      return IGRAPH_ARPACK_WORKLSMALL;
1515
0
    case -8:      return IGRAPH_ARPACK_TRIDERR;
1516
0
    case -9:      return IGRAPH_ARPACK_ZEROSTART;
1517
0
    case -10:     return IGRAPH_ARPACK_MODEINV;
1518
0
    case -11:     return IGRAPH_ARPACK_MODEBMAT;
1519
0
    case -12:     return IGRAPH_ARPACK_NEVBE;
1520
0
    case -14:     return IGRAPH_ARPACK_FAILED;
1521
0
    case -15:     return IGRAPH_ARPACK_HOWMNY;
1522
0
    case -16:     return IGRAPH_ARPACK_HOWMNYS;
1523
0
    case -17:     return IGRAPH_ARPACK_EVDIFF;
1524
0
    default:      return IGRAPH_ARPACK_UNKNOWN;
1525
0
    }
1526
1527
0
}
1528
1529
0
static igraph_arpack_error_t igraph_i_arpack_err_dnaupd(int error) {
1530
0
    switch (error) {
1531
0
    case  0:      return IGRAPH_ARPACK_NO_ERROR;
1532
0
    case  1:      return IGRAPH_ARPACK_MAXIT;
1533
0
    case  3:      return IGRAPH_ARPACK_NOSHIFT;
1534
0
    case -1:      return IGRAPH_ARPACK_NPOS;
1535
0
    case -2:      return IGRAPH_ARPACK_NEVNPOS;
1536
0
    case -3:      return IGRAPH_ARPACK_NCVSMALL;
1537
0
    case -4:      return IGRAPH_ARPACK_NONPOSI;
1538
0
    case -5:      return IGRAPH_ARPACK_WHICHINV;
1539
0
    case -6:      return IGRAPH_ARPACK_BMATINV;
1540
0
    case -7:      return IGRAPH_ARPACK_WORKLSMALL;
1541
0
    case -8:      return IGRAPH_ARPACK_TRIDERR;
1542
0
    case -9:      return IGRAPH_ARPACK_ZEROSTART;
1543
0
    case -10:     return IGRAPH_ARPACK_MODEINV;
1544
0
    case -11:     return IGRAPH_ARPACK_MODEBMAT;
1545
0
    case -12:     return IGRAPH_ARPACK_ISHIFT;
1546
0
    case -9999:   return IGRAPH_ARPACK_NOFACT;
1547
0
    default:      return IGRAPH_ARPACK_UNKNOWN;
1548
0
    }
1549
0
}
1550
1551
0
static igraph_arpack_error_t igraph_i_arpack_err_dneupd(int error) {
1552
0
    switch (error) {
1553
0
    case  0:      return IGRAPH_ARPACK_NO_ERROR;
1554
0
    case  1:      return IGRAPH_ARPACK_REORDER;
1555
0
    case -1:      return IGRAPH_ARPACK_NPOS;
1556
0
    case -2:      return IGRAPH_ARPACK_NEVNPOS;
1557
0
    case -3:      return IGRAPH_ARPACK_NCVSMALL;
1558
0
    case -5:      return IGRAPH_ARPACK_WHICHINV;
1559
0
    case -6:      return IGRAPH_ARPACK_BMATINV;
1560
0
    case -7:      return IGRAPH_ARPACK_WORKLSMALL;
1561
0
    case -8:      return IGRAPH_ARPACK_SHUR;
1562
0
    case -9:      return IGRAPH_ARPACK_LAPACK;
1563
0
    case -10:     return IGRAPH_ARPACK_MODEINV;
1564
0
    case -11:     return IGRAPH_ARPACK_MODEBMAT;
1565
0
    case -12:     return IGRAPH_ARPACK_HOWMNYS;
1566
0
    case -13:     return IGRAPH_ARPACK_HOWMNY;
1567
0
    case -14:     return IGRAPH_ARPACK_FAILED;
1568
0
    case -15:     return IGRAPH_ARPACK_EVDIFF;
1569
0
    default:      return IGRAPH_ARPACK_UNKNOWN;
1570
0
    }
1571
0
}
1572
1573
static const char* igraph_i_arpack_error_strings[] = {
1574
    /* 15 */ "Matrix-vector product failed",
1575
    /* 16 */ "N must be positive",
1576
    /* 17 */ "NEV must be positive",
1577
    /* 18 */ "NCV must be greater than NEV and less than or equal to N "
1578
    "(and for the non-symmetric solver NCV-NEV >=2 must also hold)",
1579
    /* 19 */ "Maximum number of iterations should be positive",
1580
    /* 20 */ "Invalid WHICH parameter",
1581
    /* 21 */ "Invalid BMAT parameter",
1582
    /* 22 */ "WORKL is too small",
1583
    /* 23 */ "LAPACK error in tridiagonal eigenvalue calculation",
1584
    /* 24 */ "Starting vector is zero",
1585
    /* 25 */ "MODE is invalid",
1586
    /* 26 */ "MODE and BMAT are not compatible",
1587
    /* 27 */ "ISHIFT must be 0 or 1",
1588
    /* 28 */ "NEV and WHICH='BE' are incompatible",
1589
    /* 29 */ "Could not build an Arnoldi factorization",
1590
    /* 30 */ "No eigenvalues to sufficient accuracy",
1591
    /* 31 */ "HOWMNY is invalid",
1592
    /* 32 */ "HOWMNY='S' is not implemented",
1593
    /* 33 */ "Different number of converged Ritz values",
1594
    /* 34 */ "Error from calculation of a real Schur form",
1595
    /* 35 */ "LAPACK (dtrevc) error for calculating eigenvectors",
1596
    /* 36 */ "Unknown ARPACK error",
1597
    /* 37 */ 0,
1598
    /* 38 */ 0,
1599
    /* 39 */ "Maximum number of iterations reached",
1600
    /* 40 */ "No shifts could be applied during a cycle of the "
1601
    "Implicitly restarted Arnoldi iteration. One possibility "
1602
    "is to increase the size of NCV relative to NEV",
1603
    /* 41 */ "The Schur form computed by LAPACK routine dlahqr "
1604
    "could not be reordered by LAPACK routine dtrsen."
1605
};
1606
1607
/**
1608
 * \function igraph_arpack_error_to_string
1609
 * \brief Convert an ARPACK error code to a human-readable representation.
1610
 */
1611
0
const char* igraph_arpack_error_to_string(igraph_arpack_error_t error) {
1612
0
    if (error == IGRAPH_ARPACK_NO_ERROR) {
1613
0
        return "No error";
1614
0
    }
1615
1616
0
    int index = (error >= IGRAPH_ARPACK_PROD)
1617
0
        ? error - IGRAPH_ARPACK_PROD
1618
0
        : -1;
1619
0
    const char* message = 0;
1620
1621
0
    if (index >= 0 && index < sizeof(igraph_i_arpack_error_strings) / sizeof(igraph_i_arpack_error_strings[0])) {
1622
0
        message = igraph_i_arpack_error_strings[index];
1623
0
    }
1624
1625
0
    return message ? message : "Unknown ARPACK error";
1626
0
}
1627
1628
/**
1629
 * \function igraph_arpack_get_last_error
1630
 * \brief Returns the last error code returned from an ARPACK function.
1631
 */
1632
0
igraph_arpack_error_t igraph_arpack_get_last_error(void) {
1633
0
    return last_arpack_error;
1634
0
}