Coverage Report

Created: 2025-08-25 07:03

/src/igraph/src/properties/multiplicity.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
   IGraph library.
3
   Copyright (C) 2005-2012  Gabor Csardi <csardi.gabor@gmail.com>
4
   334 Harvard street, Cambridge, MA 02139 USA
5
6
   This program is free software; you can redistribute it and/or modify
7
   it under the terms of the GNU General Public License as published by
8
   the Free Software Foundation; either version 2 of the License, or
9
   (at your option) any later version.
10
11
   This program is distributed in the hope that it will be useful,
12
   but WITHOUT ANY WARRANTY; without even the implied warranty of
13
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
   GNU General Public License for more details.
15
16
   You should have received a copy of the GNU General Public License
17
   along with this program; if not, write to the Free Software
18
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
   02110-1301 USA
20
21
*/
22
23
#include "igraph_structural.h"
24
25
#include "igraph_adjlist.h"
26
#include "igraph_interface.h"
27
28
/**
29
 * \function igraph_is_simple
30
 * \brief Decides whether the input graph is a simple graph.
31
 *
32
 * A graph is a simple graph if it does not contain loop edges and
33
 * multiple edges.
34
 *
35
 * \param graph The input graph.
36
 * \param res Pointer to a boolean constant, the result
37
 *     is stored here.
38
 * \param directed Whether to consider the directions of edges. \c IGRAPH_UNDIRECTED
39
 *     means that edge directions will be ignored and a directed graph with at
40
 *     least one mutual edge pair will be considered non-simple. \c IGRAPH_DIRECTED
41
 *     means that edge directions will be considered. Ignored for
42
 *     undirected graphs.
43
 * \return Error code.
44
 *
45
 * \sa \ref igraph_is_loop() and \ref igraph_is_multiple() to
46
 * find the loops and multiple edges, \ref igraph_simplify() to
47
 * get rid of them, or \ref igraph_has_multiple() to decide whether
48
 * there is at least one multiple edge.
49
 *
50
 * Time complexity: O(|V|+|E|).
51
 */
52
579
igraph_error_t igraph_is_simple(const igraph_t *graph, igraph_bool_t *res, igraph_bool_t directed) {
53
579
    igraph_integer_t vc = igraph_vcount(graph);
54
579
    igraph_integer_t ec = igraph_ecount(graph);
55
56
    /* Is it already known whether the graph has loops or multi-edges? */
57
579
    igraph_bool_t known_loop, known_multi;
58
59
    /* If it is known, does the graph have them? */
60
579
    igraph_bool_t has_loop, has_multi;
61
62
    /* Will we need to check mutual edges explicitly? */
63
579
    igraph_bool_t need_to_check_mutual = directed == IGRAPH_UNDIRECTED && igraph_is_directed(graph);
64
65
579
    known_loop  = igraph_i_property_cache_has(graph, IGRAPH_PROP_HAS_LOOP);
66
579
    if (known_loop) {
67
0
        has_loop = igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_HAS_LOOP);
68
0
        if (has_loop) {
69
0
            *res = false;
70
0
            goto early_exit;
71
0
        }
72
0
    }
73
74
579
    known_multi = igraph_i_property_cache_has(graph, IGRAPH_PROP_HAS_MULTI);
75
579
    if (known_multi) {
76
0
        has_multi = igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_HAS_MULTI);
77
0
        if (has_multi) {
78
0
            *res = false;
79
0
            goto early_exit;
80
0
        }
81
0
    }
82
83
579
    if (known_loop && known_multi && !has_loop && !has_multi) {
84
0
        *res = true;
85
0
        goto early_exit;
86
0
    }
87
88
    /* Up to now, these variables were used to store the cache status.
89
     * From here on, we re-use them to store the outcome of explicit
90
     * checks. */
91
92
579
    known_loop = known_multi = false;
93
579
    has_loop = has_multi = false; /* be optimistic */
94
95
579
    if (vc == 0 || ec == 0) {
96
27
        *res = true;
97
27
        known_loop = known_multi = true;
98
552
    } else {
99
552
        igraph_vector_int_t neis;
100
552
        IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0);
101
52.2k
        for (igraph_integer_t i = 0; i < vc; i++) {
102
51.6k
            IGRAPH_CHECK(igraph_neighbors(
103
51.6k
                graph, &neis, i, IGRAPH_OUT, IGRAPH_LOOPS, IGRAPH_MULTIPLE
104
51.6k
            ));
105
51.6k
            const igraph_integer_t n = igraph_vector_int_size(&neis);
106
61.5k
            for (igraph_integer_t j = 0; j < n; j++) {
107
11.1k
                if (VECTOR(neis)[j] == i) {
108
376
                    known_loop = true; has_loop = true; break;
109
376
                }
110
                /* Attention: If the graph is undirected, self-loops appears
111
                 * twice in the neighbour list. This does not mean that there
112
                 * are multi-edges. We do not need to worry about this as loop
113
                 * are already caught above. */
114
10.7k
                if (j > 0 && VECTOR(neis)[j - 1] == VECTOR(neis)[j]) {
115
875
                    known_multi = true; has_multi = true; break;
116
875
                }
117
10.7k
            }
118
51.6k
        }
119
552
        *res = !has_loop && !has_multi;
120
552
        if (*res) {
121
168
            known_multi = known_loop = true;
122
168
        }
123
552
        igraph_vector_int_destroy(&neis);
124
552
        IGRAPH_FINALLY_CLEAN(1);
125
552
    }
126
127
579
    if (known_loop) {
128
419
        igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_HAS_LOOP, has_loop);
129
419
    }
130
131
579
    if (known_multi) {
132
433
        igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_HAS_MULTI, has_multi);
133
433
    }
134
135
579
early_exit:
136
    /* If at this point we have concluded that the graph is simple, _but_ the user
137
     * asked us to ignore edge directions, we need to look for mutual edge pairs
138
     * and return false if we find one. */
139
579
    if (*res && need_to_check_mutual) {
140
        /* If the graph is undirected, we also check for mutual edges. */
141
0
        igraph_bool_t has_mutual;
142
0
        IGRAPH_CHECK(igraph_has_mutual(graph, &has_mutual, false));
143
0
        if (has_mutual) {
144
0
            *res = false;
145
0
        }
146
0
    }
147
148
579
    return IGRAPH_SUCCESS;
149
579
}
150
151
152
/**
153
 * \function igraph_has_multiple
154
 * \brief Check whether the graph has at least one multiple edge.
155
 *
156
 * An edge is a multiple edge if there is another
157
 * edge with the same head and tail vertices in the graph.
158
 *
159
 * </para><para>
160
 * The return value of this function is cached in the graph itself; calling
161
 * the function multiple times with no modifications to the graph in between
162
 * will return a cached value in O(1) time.
163
 *
164
 * \param graph The input graph.
165
 * \param res Pointer to a boolean variable, the result will be stored here.
166
 * \return Error code.
167
 *
168
 * \sa \ref igraph_count_multiple(), \ref igraph_is_multiple() and \ref igraph_simplify().
169
 *
170
 * Time complexity: O(e*d), e is the number of edges to check and d is the
171
 * average degree (out-degree in directed graphs) of the vertices at the
172
 * tail of the edges.
173
 *
174
 * \example examples/simple/igraph_has_multiple.c
175
 */
176
513
igraph_error_t igraph_has_multiple(const igraph_t *graph, igraph_bool_t *res) {
177
513
    igraph_integer_t vc = igraph_vcount(graph);
178
513
    igraph_integer_t ec = igraph_ecount(graph);
179
513
    igraph_bool_t directed = igraph_is_directed(graph);
180
181
513
    IGRAPH_RETURN_IF_CACHED_BOOL(graph, IGRAPH_PROP_HAS_MULTI, res);
182
183
513
    if (vc == 0 || ec == 0) {
184
27
        *res = false;
185
486
    } else {
186
486
        igraph_vector_int_t neis;
187
486
        igraph_integer_t i, j, n;
188
486
        igraph_bool_t found = false;
189
486
        IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0);
190
33.7k
        for (i = 0; i < vc && !found; i++) {
191
33.2k
            IGRAPH_CHECK(igraph_neighbors(
192
33.2k
                graph, &neis, i, IGRAPH_OUT, IGRAPH_LOOPS, IGRAPH_MULTIPLE
193
33.2k
            ));
194
33.2k
            n = igraph_vector_int_size(&neis);
195
36.7k
            for (j = 1; j < n; j++) {
196
3.82k
                if (VECTOR(neis)[j - 1] == VECTOR(neis)[j]) {
197
                    /* If the graph is undirected, loop edges appear twice in the neighbor
198
                     * list, so check the next item as well */
199
267
                    if (directed) {
200
                        /* Directed, so this is a real multiple edge */
201
267
                        found = true; break;
202
267
                    } else if (VECTOR(neis)[j - 1] != i) {
203
                        /* Undirected, but not a loop edge */
204
0
                        found = true; break;
205
0
                    } else if (j < n - 1 && VECTOR(neis)[j] == VECTOR(neis)[j + 1]) {
206
                        /* Undirected, loop edge, multiple times */
207
0
                        found = true; break;
208
0
                    }
209
267
                }
210
3.82k
            }
211
33.2k
        }
212
486
        *res = found;
213
486
        igraph_vector_int_destroy(&neis);
214
486
        IGRAPH_FINALLY_CLEAN(1);
215
486
    }
216
217
513
    igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_HAS_MULTI, *res);
218
219
513
    return IGRAPH_SUCCESS;
220
513
}
221
222
/**
223
 * \function igraph_is_multiple
224
 * \brief Find the multiple edges in a graph.
225
 *
226
 * An edge is a multiple edge if there is another
227
 * edge with the same head and tail vertices in the graph.
228
 *
229
 * </para><para>
230
 * Note that this function returns true only for the second or more
231
 * appearances of the multiple edges.
232
 *
233
 * \param graph The input graph.
234
 * \param res Pointer to a boolean vector, the result will be stored
235
 *        here. It will be resized as needed.
236
 * \param es The edges to check. Supply \ref igraph_ess_all() if you want
237
 *        to check all edges.
238
 * \return Error code.
239
 *
240
 * \sa \ref igraph_count_multiple(), \ref igraph_count_multiple_1(),
241
 * \ref igraph_has_multiple() and \ref igraph_simplify().
242
 *
243
 * Time complexity: O(e*d), e is the number of edges to check and d is the
244
 * average degree (out-degree in directed graphs) of the vertices at the
245
 * tail of the edges.
246
 *
247
 * \example examples/simple/igraph_is_multiple.c
248
 */
249
igraph_error_t igraph_is_multiple(const igraph_t *graph, igraph_vector_bool_t *res,
250
0
                                  igraph_es_t es) {
251
0
    igraph_eit_t eit;
252
0
    igraph_integer_t i, j, n;
253
0
    igraph_lazy_inclist_t inclist;
254
255
0
    IGRAPH_CHECK(igraph_eit_create(graph, es, &eit));
256
0
    IGRAPH_FINALLY(igraph_eit_destroy, &eit);
257
258
0
    IGRAPH_CHECK(igraph_lazy_inclist_init(graph, &inclist, IGRAPH_OUT, IGRAPH_LOOPS_ONCE));
259
0
    IGRAPH_FINALLY(igraph_lazy_inclist_destroy, &inclist);
260
261
0
    IGRAPH_CHECK(igraph_vector_bool_resize(res, IGRAPH_EIT_SIZE(eit)));
262
263
0
    for (i = 0; !IGRAPH_EIT_END(eit); i++, IGRAPH_EIT_NEXT(eit)) {
264
0
        igraph_integer_t e = IGRAPH_EIT_GET(eit);
265
0
        igraph_integer_t from = IGRAPH_FROM(graph, e);
266
0
        igraph_integer_t to = IGRAPH_TO(graph, e);
267
0
        igraph_vector_int_t *neis = igraph_lazy_inclist_get(&inclist, from);
268
269
0
        IGRAPH_CHECK_OOM(neis, "Failed to query incident edges.");
270
271
0
        VECTOR(*res)[i] = false;
272
273
0
        n = igraph_vector_int_size(neis);
274
0
        for (j = 0; j < n; j++) {
275
0
            igraph_integer_t e2 = VECTOR(*neis)[j];
276
0
            igraph_integer_t to2 = IGRAPH_OTHER(graph, e2, from);
277
0
            if (to2 == to && e2 < e) {
278
0
                VECTOR(*res)[i] = true;
279
0
            }
280
0
        }
281
0
    }
282
283
0
    igraph_lazy_inclist_destroy(&inclist);
284
0
    igraph_eit_destroy(&eit);
285
0
    IGRAPH_FINALLY_CLEAN(2);
286
0
    return IGRAPH_SUCCESS;
287
0
}
288
289
/**
290
 * \function igraph_count_multiple
291
 * \brief The multiplicity of some edges in a graph.
292
 *
293
 * An edge is called a multiple edge when there is one or more other
294
 * edge between the same two vertices. The multiplicity of an edge
295
 * is the number of edges between its endpoints.
296
 *
297
 * \param graph The input graph.
298
 * \param res Pointer to a vector, the result will be stored
299
 *        here. It will be resized as needed.
300
 * \param es The edges to check. Supply \ref igraph_ess_all() if you want
301
 *        to check all edges.
302
 * \return Error code.
303
 *
304
 * \sa \ref igraph_count_multiple_1() if you only need the multiplicity of a
305
 * single edge; \ref igraph_is_multiple() if you are only interested in whether
306
 * the graph has at least one edge with multiplicity greater than one;
307
 * \ref igraph_simplify() to ensure that the graph has no multiple edges.
308
 *
309
 * Time complexity: O(E d), E is the number of edges to check and d is the
310
 * average degree (out-degree in directed graphs) of the vertices at the
311
 * tail of the edges.
312
 */
313
igraph_error_t igraph_count_multiple(const igraph_t *graph, igraph_vector_int_t *res,
314
0
                                     igraph_es_t es) {
315
0
    igraph_eit_t eit;
316
0
    igraph_integer_t i, j, n;
317
0
    igraph_lazy_adjlist_t adjlist;
318
319
0
    IGRAPH_CHECK(igraph_eit_create(graph, es, &eit));
320
0
    IGRAPH_FINALLY(igraph_eit_destroy, &eit);
321
0
    IGRAPH_CHECK(igraph_lazy_adjlist_init(graph, &adjlist, IGRAPH_OUT, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE));
322
0
    IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &adjlist);
323
324
0
    IGRAPH_CHECK(igraph_vector_int_resize(res, IGRAPH_EIT_SIZE(eit)));
325
326
0
    for (i = 0; !IGRAPH_EIT_END(eit); i++, IGRAPH_EIT_NEXT(eit)) {
327
0
        igraph_integer_t e = IGRAPH_EIT_GET(eit);
328
0
        igraph_integer_t from = IGRAPH_FROM(graph, e);
329
0
        igraph_integer_t to = IGRAPH_TO(graph, e);
330
0
        igraph_vector_int_t *neis = igraph_lazy_adjlist_get(&adjlist, from);
331
332
0
        IGRAPH_CHECK_OOM(neis, "Failed to query adjacent vertices.");
333
334
0
        VECTOR(*res)[i] = 0;
335
336
0
        n = igraph_vector_int_size(neis);
337
0
        for (j = 0; j < n; j++) {
338
0
            if (VECTOR(*neis)[j] == to) {
339
0
                VECTOR(*res)[i]++;
340
0
            }
341
0
        }
342
0
    }
343
344
0
    igraph_lazy_adjlist_destroy(&adjlist);
345
0
    igraph_eit_destroy(&eit);
346
0
    IGRAPH_FINALLY_CLEAN(2);
347
348
0
    return IGRAPH_SUCCESS;
349
0
}
350
351
/**
352
 * \function igraph_count_multiple_1
353
 * \brief The multiplicity of a single edge in a graph.
354
 *
355
 * \param graph The input graph.
356
 * \param res Pointer to an iteger, the result will be stored here.
357
 * \param eid The ID of the edge to check.
358
 * \return Error code.
359
 *
360
 * \sa \ref igraph_count_multiple() if you need the multiplicity of multiple
361
 * edges; \ref igraph_is_multiple() if you are only interested in whether the
362
 * graph has at least one edge with multiplicity greater than one;
363
 * \ref igraph_simplify() to ensure that the graph has no multiple edges.
364
 *
365
 * Time complexity: O(d), where d is the out-degree of the tail of the edge.
366
 */
367
igraph_error_t igraph_count_multiple_1(const igraph_t *graph, igraph_integer_t *res,
368
                                       igraph_integer_t eid)
369
0
{
370
0
    igraph_integer_t i, n, count;
371
0
    igraph_integer_t from = IGRAPH_FROM(graph, eid);
372
0
    igraph_integer_t to = IGRAPH_TO(graph, eid);
373
0
    igraph_vector_int_t vids;
374
375
0
    IGRAPH_VECTOR_INT_INIT_FINALLY(&vids, 0);
376
0
    IGRAPH_CHECK(igraph_neighbors(
377
0
        graph, &vids, from, IGRAPH_OUT, IGRAPH_LOOPS, IGRAPH_MULTIPLE
378
0
    ));
379
380
0
    count = 0;
381
0
    n = igraph_vector_int_size(&vids);
382
0
    for (i = 0; i < n; i++) {
383
0
        if (VECTOR(vids)[i] == to) {
384
0
            count++;
385
0
        }
386
0
    }
387
388
0
    igraph_vector_int_destroy(&vids);
389
0
    IGRAPH_FINALLY_CLEAN(1);
390
391
0
    *res = count;
392
393
0
    return IGRAPH_SUCCESS;
394
0
}
395
396
/**
397
 * \function igraph_is_mutual
398
 * \brief Check whether some edges of a directed graph are mutual.
399
 *
400
 * An (A,B) non-loop directed edge is mutual if the graph contains
401
 * the (B,A) edge too. Whether directed self-loops are considered mutual
402
 * is controlled by the \p loops parameter.
403
 *
404
 * </para><para>
405
 * An undirected graph only has mutual edges, by definition.
406
 *
407
 * </para><para>
408
 * Edge multiplicity is not considered here, e.g. if there are two
409
 * (A,B) edges and one (B,A) edge, then all three are considered to be
410
 * mutual.
411
 *
412
 * \param graph The input graph.
413
 * \param res Pointer to an initialized vector, the result is stored
414
 *        here.
415
 * \param es The sequence of edges to check. Supply
416
 *        \ref igraph_ess_all() to check all edges.
417
 * \param loops Boolean, whether to consider directed self-loops
418
 *        to be mutual.
419
 * \return Error code.
420
 *
421
 * Time complexity: O(n log(d)), n is the number of edges supplied, d
422
 * is the maximum in-degree of the vertices that are targets of the
423
 * supplied edges. An upper limit of the time complexity is O(n log(|E|)),
424
 * |E| is the number of edges in the graph.
425
 */
426
igraph_error_t igraph_is_mutual(const igraph_t *graph, igraph_vector_bool_t *res,
427
0
                                igraph_es_t es, igraph_bool_t loops) {
428
429
0
    igraph_eit_t eit;
430
0
    igraph_lazy_adjlist_t adjlist;
431
0
    igraph_integer_t i;
432
433
    /* How many edges do we have? */
434
0
    IGRAPH_CHECK(igraph_eit_create(graph, es, &eit));
435
0
    IGRAPH_FINALLY(igraph_eit_destroy, &eit);
436
0
    IGRAPH_CHECK(igraph_vector_bool_resize(res, IGRAPH_EIT_SIZE(eit)));
437
438
    /* An undirected graph has mutual edges by definition,
439
       res is already properly resized */
440
0
    if (! igraph_is_directed(graph)) {
441
0
        igraph_vector_bool_fill(res, true);
442
0
        igraph_eit_destroy(&eit);
443
0
        IGRAPH_FINALLY_CLEAN(1);
444
0
        return IGRAPH_SUCCESS;
445
0
    }
446
447
0
    IGRAPH_CHECK(igraph_lazy_adjlist_init(graph, &adjlist, IGRAPH_OUT, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE));
448
0
    IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &adjlist);
449
450
0
    for (i = 0; ! IGRAPH_EIT_END(eit); i++, IGRAPH_EIT_NEXT(eit)) {
451
0
        igraph_integer_t edge = IGRAPH_EIT_GET(eit);
452
0
        igraph_integer_t from = IGRAPH_FROM(graph, edge);
453
0
        igraph_integer_t to = IGRAPH_TO(graph, edge);
454
455
0
        if (from == to) {
456
0
            VECTOR(*res)[i] = loops;
457
0
            continue; /* no need to do binsearch for self-loops */
458
0
        }
459
460
        /* Check whether there is a to->from edge, search for from in the
461
           out-list of to */
462
0
        igraph_vector_int_t *neis = igraph_lazy_adjlist_get(&adjlist, to);
463
0
        IGRAPH_CHECK_OOM(neis, "Failed to query neighbors.");
464
0
        VECTOR(*res)[i] = igraph_vector_int_contains_sorted(neis, from);
465
0
    }
466
467
0
    igraph_lazy_adjlist_destroy(&adjlist);
468
0
    igraph_eit_destroy(&eit);
469
0
    IGRAPH_FINALLY_CLEAN(2);
470
471
0
    return IGRAPH_SUCCESS;
472
0
}
473
474
475
/**
476
 * \function igraph_has_mutual
477
 * \brief Check whether a directed graph has any mutual edges.
478
 *
479
 * An (A,B) non-loop directed edge is mutual if the graph contains
480
 * the (B,A) edge too. Whether directed self-loops are considered mutual
481
 * is controlled by the \p loops parameter.
482
 *
483
 * </para><para>
484
 * In undirected graphs, all edges are considered mutual by definition.
485
 * Thus for undirected graph, this function returns false only when there
486
 * are no edges.
487
 *
488
 * </para><para>
489
 * To check whether a graph is an oriented graph, use this function in
490
 * conjunction with \ref igraph_is_directed().
491
 *
492
 * \param graph The input graph.
493
 * \param res Pointer to a boolean, the result will be stored here.
494
 * \param loops Boolean, whether to consider directed self-loops
495
 *        to be mutual.
496
 * \return Error code.
497
 *
498
 * Time complexity: O(|E| log(d)) where d is the maximum in-degree.
499
 */
500
igraph_error_t igraph_has_mutual(const igraph_t *graph, igraph_bool_t *res,
501
513
                                 igraph_bool_t loops) {
502
513
    igraph_integer_t no_of_edges = igraph_ecount(graph);
503
513
    igraph_lazy_adjlist_t adjlist;
504
505
513
    if (! igraph_is_directed(graph)) {
506
        /* In undirected graphs, all edges are considered mutual, so we just check
507
         * if there are any edges. */
508
0
        *res = no_of_edges > 0;
509
0
        return IGRAPH_SUCCESS;
510
0
    }
511
512
513
    if (igraph_i_property_cache_has(graph, IGRAPH_PROP_HAS_MUTUAL)) {
513
0
        if (igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_HAS_MUTUAL)) {
514
            /* we know that the graph has at least one mutual non-loop edge
515
             * (because the cache only stores non-loop edges) */
516
0
            *res = true;
517
0
            return IGRAPH_SUCCESS;
518
0
        } else if (loops) {
519
            /* no non-loop mutual edges, but maybe we have loops? */
520
0
            return igraph_has_loop(graph, res);
521
0
        } else {
522
            /* no non-loop mutual edges, and loops are not to be treated as mutual */
523
0
            *res = false;
524
0
            return IGRAPH_SUCCESS;
525
0
        }
526
0
    }
527
528
513
    IGRAPH_CHECK(igraph_lazy_adjlist_init(graph, &adjlist, IGRAPH_OUT, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE));
529
513
    IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &adjlist);
530
531
513
    *res = false; /* assume no mutual edges */
532
18.1k
    for (igraph_integer_t edge=0; edge < no_of_edges; edge++) {
533
17.7k
        igraph_integer_t from = IGRAPH_FROM(graph, edge);
534
17.7k
        igraph_integer_t to = IGRAPH_TO(graph, edge);
535
536
17.7k
        if (from == to) {
537
5.37k
            if (loops) {
538
0
                *res = true;
539
0
                break;
540
0
            }
541
5.37k
            continue; /* no need to do binsearch for self-loops */
542
5.37k
        }
543
544
        /* Check whether there is a to->from edge, search for from in the
545
           out-list of to */
546
12.3k
        igraph_vector_int_t *neis = igraph_lazy_adjlist_get(&adjlist, to);
547
12.3k
        IGRAPH_CHECK_OOM(neis, "Failed to query neighbors.");
548
12.3k
        if (igraph_vector_int_contains_sorted(neis, from)) {
549
74
            *res = true;
550
74
            break;
551
74
        }
552
12.3k
    }
553
554
513
    igraph_lazy_adjlist_destroy(&adjlist);
555
513
    IGRAPH_FINALLY_CLEAN(1);
556
557
    /* cache the result if loops are not treated as mutual */
558
513
    if (!loops) {
559
513
        igraph_i_property_cache_set_bool_checked(graph, IGRAPH_PROP_HAS_MUTUAL, *res);
560
513
    }
561
562
513
    return IGRAPH_SUCCESS;
563
513
}