Coverage Report

Created: 2023-09-25 06:03

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