Coverage Report

Created: 2025-11-21 06:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/igraph/src/misc/graphicality.c
Line
Count
Source
1
/*
2
   igraph library.
3
   Copyright (C) 2020  The igraph development team <igraph@igraph.org>
4
5
   This program is free software; you can redistribute it and/or modify
6
   it under the terms of the GNU General Public License as published by
7
   the Free Software Foundation; either version 2 of the License, or
8
   (at your option) any later version.
9
10
   This program is distributed in the hope that it will be useful,
11
   but WITHOUT ANY WARRANTY; without even the implied warranty of
12
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
   GNU General Public License for more details.
14
15
   You should have received a copy of the GNU General Public License
16
   along with this program.  If not, see <https://www.gnu.org/licenses/>.
17
*/
18
19
#include "igraph_graphicality.h"
20
21
#include "misc/graphicality.h"
22
23
24
igraph_error_t igraph_i_edge_type_to_loops_multiple(
25
    igraph_edge_type_sw_t allowed_edge_types,
26
0
    igraph_bool_t *loops, igraph_bool_t *multiple) {
27
28
0
    *loops = (allowed_edge_types & IGRAPH_LOOPS_SW) ? true : false;
29
0
    *multiple = (allowed_edge_types & IGRAPH_I_MULTI_EDGES_SW) ? true : false;
30
31
0
    if (*loops) {
32
0
        igraph_bool_t multi_loops = (allowed_edge_types & IGRAPH_I_MULTI_LOOPS_SW);
33
0
        if (*multiple != multi_loops) {
34
0
            IGRAPH_ERROR("Either both multi-edges and multi-loops should be allowed or neither.",
35
0
                         IGRAPH_EINVAL);
36
0
        }
37
0
    }
38
39
0
    return IGRAPH_SUCCESS;
40
0
}
41
42
43
static igraph_error_t igraph_i_is_graphical_undirected_multi_loops(const igraph_vector_int_t *degrees, igraph_bool_t *res);
44
static igraph_error_t igraph_i_is_graphical_undirected_loopless_multi(const igraph_vector_int_t *degrees, igraph_bool_t *res);
45
static igraph_error_t igraph_i_is_graphical_undirected_loopy_simple(const igraph_vector_int_t *degrees, igraph_bool_t *res);
46
static igraph_error_t igraph_i_is_graphical_undirected_simple(const igraph_vector_int_t *degrees, igraph_bool_t *res);
47
48
static igraph_error_t igraph_i_is_graphical_directed_loopy_multi(const igraph_vector_int_t *out_degrees, const igraph_vector_int_t *in_degrees, igraph_bool_t *res);
49
static igraph_error_t igraph_i_is_graphical_directed_loopless_multi(const igraph_vector_int_t *out_degrees, const igraph_vector_int_t *in_degrees, igraph_bool_t *res);
50
static igraph_error_t igraph_i_is_graphical_directed_loopy_simple(const igraph_vector_int_t *out_degrees, const igraph_vector_int_t *in_degrees, igraph_bool_t *res);
51
static igraph_error_t igraph_i_is_graphical_directed_simple(const igraph_vector_int_t *out_degrees, const igraph_vector_int_t *in_degrees, igraph_bool_t *res);
52
53
static igraph_error_t igraph_i_is_bigraphical_multi(const igraph_vector_int_t *degrees1, const igraph_vector_int_t *degrees2, igraph_bool_t *res);
54
static igraph_error_t igraph_i_is_bigraphical_simple(const igraph_vector_int_t *degrees1, const igraph_vector_int_t *degrees2, igraph_bool_t *res);
55
56
57
/**
58
 * \function igraph_is_graphical
59
 * \brief Is there a graph with the given degree sequence?
60
 *
61
 * Determines whether a sequence of integers can be the degree sequence of some graph.
62
 * The classical concept of graphicality assumes simple graphs. This function can perform
63
 * the check also when either self-loops, multi-edge, or both are allowed in the graph.
64
 *
65
 * </para><para>
66
 * For simple undirected graphs, the Erdős-Gallai conditions are checked using the linear-time
67
 * algorithm of Cloteaux. If both self-loops and multi-edges are allowed,
68
 * it is sufficient to chek that that sum of degrees is even. If only multi-edges are allowed, but
69
 * not self-loops, there is an additional condition that the sum of degrees be no smaller than twice
70
 * the maximum degree. If at most one self-loop is allowed per vertex, but no multi-edges, a modified
71
 * version of the Erdős-Gallai conditions are used (see Cairns &amp; Mendan).
72
 *
73
 * </para><para>
74
 * For simple directed graphs, the Fulkerson-Chen-Anstee theorem is used with the relaxation by Berger.
75
 * If both self-loops and multi-edges are allowed, then it is sufficient to check that the sum of
76
 * in- and out-degrees is the same. If only multi-edges are allowed, but not self loops, there is an
77
 * additional condition that the sum of out-degrees (or equivalently, in-degrees) is no smaller than
78
 * the maximum total degree. If single self-loops are allowed, but not multi-edges, the problem is equivalent
79
 * to realizability as a simple bipartite graph, thus the Gale-Ryser theorem can be used; see
80
 * \ref igraph_is_bigraphical() for more information.
81
 *
82
 * </para><para>
83
 * References:
84
 *
85
 * </para><para>
86
 * P. Erdős and T. Gallai, Gráfok előírt fokú pontokkal, Matematikai Lapok 11, pp. 264–274 (1960).
87
 * https://users.renyi.hu/~p_erdos/1961-05.pdf
88
 *
89
 * </para><para>
90
 * Z. Király, Recognizing graphic degree sequences and generating all realizations.
91
 * TR-2011-11, Egerváry Research Group, H-1117, Budapest, Hungary. ISSN 1587-4451 (2012).
92
 * https://egres.elte.hu/tr/egres-11-11.pdf
93
 *
94
 * </para><para>
95
 * B. Cloteaux, Is This for Real? Fast Graphicality Testing, Comput. Sci. Eng. 17, 91 (2015).
96
 * https://dx.doi.org/10.1109/MCSE.2015.125
97
 *
98
 * </para><para>
99
 * A. Berger, A note on the characterization of digraphic sequences, Discrete Math. 314, 38 (2014).
100
 * https://dx.doi.org/10.1016/j.disc.2013.09.010
101
 *
102
 * </para><para>
103
 * G. Cairns and S. Mendan, Degree Sequence for Graphs with Loops (2013).
104
 * https://arxiv.org/abs/1303.2145v1
105
 *
106
 * \param out_degrees A vector of integers specifying the degree sequence for
107
 *     undirected graphs or the out-degree sequence for directed graphs.
108
 * \param in_degrees A vector of integers specifying the in-degree sequence for
109
 *     directed graphs. For undirected graphs, it must be \c NULL.
110
 * \param allowed_edge_types The types of edges to allow in the graph. See
111
 *     \ref igraph_edge_type_sw_t for details.
112
 *     \clist
113
 *     \cli IGRAPH_SIMPLE_SW
114
 *       simple graphs (i.e. no self-loops or multi-edges allowed).
115
 *     \cli IGRAPH_LOOPS_SW
116
 *       single self-loops are allowed, but not multi-edges.
117
 *     \cli IGRAPH_MULTI_SW
118
 *       multi-edges are allowed, but not self-loops.
119
 *     \cli IGRAPH_LOOPS_SW | IGRAPH_MULTI_SW
120
 *       both self-loops and multi-edges are allowed.
121
 *     \endclist
122
 * \param res Pointer to a Boolean. The result will be stored here.
123
 *
124
 * \return Error code.
125
 *
126
 * \sa \ref igraph_is_bigraphical() to check if a bi-degree-sequence can be realized as a bipartite graph;
127
 * \ref igraph_realize_degree_sequence() to construct a graph with a given degree sequence.
128
 *
129
 * Time complexity: O(n), where n is the length of the degree sequence(s).
130
 */
131
igraph_error_t igraph_is_graphical(const igraph_vector_int_t *out_degrees,
132
                        const igraph_vector_int_t *in_degrees,
133
                        const igraph_edge_type_sw_t allowed_edge_types,
134
                        igraph_bool_t *res)
135
0
{
136
    /* Undirected case: */
137
0
    if (in_degrees == NULL)
138
0
    {
139
0
        if ( (allowed_edge_types & IGRAPH_LOOPS_SW) && (allowed_edge_types & IGRAPH_I_MULTI_LOOPS_SW )) {
140
            /* Typically this case is used when multiple edges are allowed both as self-loops and
141
             * between distinct vertices. However, the conditions are the same even if multi-edges
142
             * are not allowed between distinct vertices (only as self-loops). Therefore, we
143
             * do not test IGRAPH_I_MULTI_EDGES_SW in the if (...). */
144
0
            return igraph_i_is_graphical_undirected_multi_loops(out_degrees, res);
145
0
        }
146
0
        else if ( ! (allowed_edge_types & IGRAPH_LOOPS_SW) && (allowed_edge_types & IGRAPH_I_MULTI_EDGES_SW) ) {
147
0
            return igraph_i_is_graphical_undirected_loopless_multi(out_degrees, res);
148
0
        }
149
0
        else if ( (allowed_edge_types & IGRAPH_LOOPS_SW) && ! (allowed_edge_types & IGRAPH_I_MULTI_LOOPS_SW) && ! (allowed_edge_types & IGRAPH_I_MULTI_EDGES_SW) ) {
150
0
            return igraph_i_is_graphical_undirected_loopy_simple(out_degrees, res);
151
0
        }
152
0
        else if ( ! (allowed_edge_types & IGRAPH_LOOPS_SW) && ! (allowed_edge_types & IGRAPH_I_MULTI_EDGES_SW) ) {
153
0
            return igraph_i_is_graphical_undirected_simple(out_degrees, res);
154
0
        } else {
155
            /* Remaining case:
156
             *  - At most one self-loop per vertex but multi-edges between distinct vertices allowed.
157
             * These cases cannot currently be requested through the documented API,
158
             * so no explanatory error message for now. */
159
0
            return IGRAPH_UNIMPLEMENTED;
160
0
        }
161
0
    }
162
    /* Directed case: */
163
0
    else
164
0
    {
165
0
        if (igraph_vector_int_size(in_degrees) != igraph_vector_int_size(out_degrees)) {
166
0
            IGRAPH_ERROR("The length of out- and in-degree sequences must be the same.", IGRAPH_EINVAL);
167
0
        }
168
169
0
        if ( (allowed_edge_types & IGRAPH_LOOPS_SW) && (allowed_edge_types & IGRAPH_I_MULTI_EDGES_SW) && (allowed_edge_types & IGRAPH_I_MULTI_LOOPS_SW ) ) {
170
0
            return igraph_i_is_graphical_directed_loopy_multi(out_degrees, in_degrees, res);
171
0
        }
172
0
        else if ( ! (allowed_edge_types & IGRAPH_LOOPS_SW) && (allowed_edge_types & IGRAPH_I_MULTI_EDGES_SW) ) {
173
0
            return igraph_i_is_graphical_directed_loopless_multi(out_degrees, in_degrees, res);
174
0
        }
175
0
        else if ( (allowed_edge_types & IGRAPH_LOOPS_SW) && ! (allowed_edge_types & IGRAPH_I_MULTI_LOOPS_SW) && ! (allowed_edge_types & IGRAPH_I_MULTI_EDGES_SW) ) {
176
0
            return igraph_i_is_graphical_directed_loopy_simple(out_degrees, in_degrees, res);
177
0
        }
178
0
        else if ( ! (allowed_edge_types & IGRAPH_LOOPS_SW) && ! (allowed_edge_types & IGRAPH_I_MULTI_EDGES_SW) ) {
179
0
            return igraph_i_is_graphical_directed_simple(out_degrees, in_degrees, res);
180
0
        } else {
181
            /* Remaining cases:
182
             *  - At most one self-loop per vertex but multi-edges between distinct vertices allowed.
183
             *  - At most one edge between distinct vertices but multi-self-loops allowed.
184
             * These cases cannot currently be requested through the documented API,
185
             * so no explanatory error message for now. */
186
0
            return IGRAPH_UNIMPLEMENTED;
187
0
        }
188
0
    }
189
190
    /* can't reach here */
191
0
}
192
193
/**
194
 * \function igraph_is_bigraphical
195
 * \brief Is there a bipartite graph with the given bi-degree-sequence?
196
 *
197
 * Determines whether two sequences of integers can be the degree sequences of
198
 * a bipartite graph. Such a pair of degree sequence is called \em bigraphical.
199
 *
200
 * </para><para>
201
 * When multi-edges are allowed, it is sufficient to check that the sum of degrees is the
202
 * same in the two partitions. For simple graphs, the Gale-Ryser theorem is used
203
 * with Berger's relaxation.
204
 *
205
 * </para><para>
206
 * References:
207
 *
208
 * </para><para>
209
 * H. J. Ryser, Combinatorial Properties of Matrices of Zeros and Ones, Can. J. Math. 9, 371 (1957).
210
 * https://dx.doi.org/10.4153/cjm-1957-044-3
211
 *
212
 * </para><para>
213
 * D. Gale, A theorem on flows in networks, Pacific J. Math. 7, 1073 (1957).
214
 * https://dx.doi.org/10.2140/pjm.1957.7.1073
215
 *
216
 * </para><para>
217
 * A. Berger, A note on the characterization of digraphic sequences, Discrete Math. 314, 38 (2014).
218
 * https://dx.doi.org/10.1016/j.disc.2013.09.010
219
 *
220
 * \param degrees1 A vector of integers specifying the degrees in the first partition
221
 * \param degrees2 A vector of integers specifying the degrees in the second partition
222
 * \param allowed_edge_types The types of edges to allow in the graph:
223
 *     \clist
224
 *     \cli IGRAPH_SIMPLE_SW
225
 *       simple graphs (i.e. no multi-edges allowed).
226
 *     \cli IGRAPH_MULTI_SW
227
 *       multi-edges are allowed.
228
 *     \endclist
229
 * \param res Pointer to a Boolean. The result will be stored here.
230
 *
231
 * \return Error code.
232
 *
233
 * \sa \ref igraph_is_graphical()
234
 *
235
 * Time complexity: O(n), where n is the length of the larger degree sequence.
236
 */
237
igraph_error_t igraph_is_bigraphical(const igraph_vector_int_t *degrees1,
238
                          const igraph_vector_int_t *degrees2,
239
                          const igraph_edge_type_sw_t allowed_edge_types,
240
                          igraph_bool_t *res)
241
0
{
242
    /* Note: Bipartite graphs can't have self-loops so we ignore the IGRAPH_LOOPS_SW bit. */
243
0
    if (allowed_edge_types & IGRAPH_I_MULTI_EDGES_SW) {
244
0
        return igraph_i_is_bigraphical_multi(degrees1, degrees2, res);
245
0
    } else {
246
0
        return igraph_i_is_bigraphical_simple(degrees1, degrees2, res);
247
0
    }
248
0
}
249
250
251
/***** Undirected case *****/
252
253
/* Undirected graph with multi-self-loops:
254
 *  - Degrees must be non-negative.
255
 *  - The sum of degrees must be even.
256
 *
257
 * These conditions are valid regardless of whether multi-edges are allowed between distinct vertices.
258
 */
259
0
static igraph_error_t igraph_i_is_graphical_undirected_multi_loops(const igraph_vector_int_t *degrees, igraph_bool_t *res) {
260
0
    igraph_int_t sum_parity = 0; /* 0 if the degree sum is even, 1 if it is odd */
261
0
    igraph_int_t n = igraph_vector_int_size(degrees);
262
0
    igraph_int_t i;
263
264
0
    for (i = 0; i < n; ++i) {
265
0
        igraph_int_t d = VECTOR(*degrees)[i];
266
267
0
        if (d < 0) {
268
0
            *res = false;
269
0
            return IGRAPH_SUCCESS;
270
0
        }
271
0
        sum_parity = (sum_parity + d) & 1;
272
0
    }
273
274
0
    *res = (sum_parity == 0);
275
276
0
    return IGRAPH_SUCCESS;
277
0
}
278
279
280
/* Undirected loopless multigraph:
281
 *  - Degrees must be non-negative.
282
 *  - The sum of degrees must be even.
283
 *  - The sum of degrees must be no smaller than 2*d_max.
284
 */
285
0
static igraph_error_t igraph_i_is_graphical_undirected_loopless_multi(const igraph_vector_int_t *degrees, igraph_bool_t *res) {
286
0
    igraph_int_t i;
287
0
    igraph_int_t n = igraph_vector_int_size(degrees);
288
0
    igraph_int_t dsum, dmax;
289
290
    /* Zero-length sequences are considered graphical. */
291
0
    if (n == 0) {
292
0
        *res = true;
293
0
        return IGRAPH_SUCCESS;
294
0
    }
295
296
0
    dsum = 0; dmax = 0;
297
0
    for (i = 0; i < n; ++i) {
298
0
        igraph_int_t d = VECTOR(*degrees)[i];
299
300
0
        if (d < 0) {
301
0
            *res = false;
302
0
            return IGRAPH_SUCCESS;
303
0
        }
304
0
        dsum += d;
305
0
        if (d > dmax) {
306
0
            dmax = d;
307
0
        }
308
0
    }
309
310
0
    *res = (dsum % 2 == 0) && (dsum >= 2*dmax);
311
312
0
    return IGRAPH_SUCCESS;
313
0
}
314
315
316
/* Undirected graph with no multi-edges and at most one self-loop per vertex:
317
 *  - Degrees must be non-negative.
318
 *  - The sum of degrees must be even.
319
 *  - Use the modification of the Erdős-Gallai theorem due to Cairns and Mendan.
320
 */
321
0
static igraph_error_t igraph_i_is_graphical_undirected_loopy_simple(const igraph_vector_int_t *degrees, igraph_bool_t *res) {
322
0
    igraph_vector_int_t num_degs;
323
0
    igraph_int_t w, b, s, c, n, k, wd, kd;
324
325
0
    n = igraph_vector_int_size(degrees);
326
327
    /* Zero-length sequences are considered graphical. */
328
0
    if (n == 0) {
329
0
        *res = true;
330
0
        return IGRAPH_SUCCESS;
331
0
    }
332
333
    /* The conditions from the loopy multigraph case are necessary here as well. */
334
0
    IGRAPH_CHECK(igraph_i_is_graphical_undirected_multi_loops(degrees, res));
335
0
    if (! *res) {
336
0
        return IGRAPH_SUCCESS;
337
0
    }
338
339
    /*
340
     * We follow this paper:
341
     *
342
     * G. Cairns & S. Mendan: Degree Sequences for Graphs with Loops, 2013
343
     * https://arxiv.org/abs/1303.2145v1
344
     *
345
     * They give the following modification of the Erdős-Gallai theorem:
346
     *
347
     * A non-increasing degree sequence d_1 >= ... >= d_n has a realization as
348
     * a simple graph with loops (i.e. at most one self-loop allowed on each vertex)
349
     * iff
350
     *
351
     * \sum_{i=1}^k d_i <= k(k+1) + \sum_{i=k+1}^{n} min(d_i, k)
352
     *
353
     * for each k=1..n
354
     *
355
     * The difference from Erdős-Gallai is that here we have the term
356
     * k(k+1) instead of k(k-1).
357
     *
358
     * The implementation is analogous to igraph_i_is_graphical_undirected_simple(),
359
     * which in turn is based on Király 2012. See comments in that function for details.
360
     * w and k are zero-based here, unlike in the statement of the theorem above.
361
     */
362
363
0
    IGRAPH_VECTOR_INT_INIT_FINALLY(&num_degs, n+2);
364
365
0
    for (igraph_int_t i = 0; i < n; ++i) {
366
0
        igraph_int_t degree = VECTOR(*degrees)[i];
367
368
        /* Negative degrees are already checked in igraph_i_is_graphical_undirected_multi_loops() */
369
0
        if (degree > n+1) {
370
0
            *res = false;
371
0
            goto undirected_loopy_simple_finish;
372
0
        }
373
374
0
        ++VECTOR(num_degs)[degree];
375
0
    }
376
377
    /* Convert num_degs to a cumulative sum array. */
378
0
    for (igraph_int_t d = n; d >= 0; --d) {
379
0
        VECTOR(num_degs)[d] += VECTOR(num_degs)[d+1];
380
0
    }
381
382
0
    wd = 0, kd = n+1;
383
0
    *res = true;
384
0
    w = n - 1; b = 0; s = 0; c = 0;
385
0
    for (k = 0; k < n; k++) {
386
0
        while (k >= VECTOR(num_degs)[kd]) {
387
0
            --kd;
388
0
        }
389
0
        b += kd;
390
0
        c += w;
391
0
        while (w > k) {
392
0
            while (wd + 1 <= n + 1 && w < VECTOR(num_degs)[wd + 1]) {
393
0
                wd++;
394
0
            }
395
0
            if (wd > k + 1) break;
396
0
            s += wd;
397
0
            c -= (k + 1);
398
0
            w--;
399
0
        }
400
0
        if (b > c + s + 2*(k + 1)) {
401
0
            *res = false;
402
0
            break;
403
0
        }
404
0
        if (w == k) {
405
0
            break;
406
0
        }
407
0
    }
408
409
0
undirected_loopy_simple_finish:
410
0
    igraph_vector_int_destroy(&num_degs);
411
0
    IGRAPH_FINALLY_CLEAN(1);
412
413
0
    return IGRAPH_SUCCESS;
414
0
}
415
416
417
/* Undirected simple graph:
418
 *  - Degrees must be non-negative.
419
 *  - The sum of degrees must be even.
420
 *  - Use the Erdős-Gallai theorem.
421
 */
422
0
static igraph_error_t igraph_i_is_graphical_undirected_simple(const igraph_vector_int_t *degrees, igraph_bool_t *res) {
423
0
    igraph_vector_int_t num_degs; /* num_degs[d] is the # of vertices with degree d */
424
0
    const igraph_int_t p = igraph_vector_int_size(degrees);
425
0
    igraph_int_t dmin, dmax, dsum;
426
0
    igraph_int_t n; /* number of non-zero degrees */
427
0
    igraph_int_t k, sum_deg, sum_ni, sum_ini;
428
0
    igraph_int_t i, dk;
429
0
    igraph_int_t zverovich_bound;
430
431
0
    if (p == 0) {
432
0
        *res = true;
433
0
        return IGRAPH_SUCCESS;
434
0
    }
435
436
    /* The following implementation of the Erdős-Gallai test
437
     * is mostly a direct translation of the Python code given in
438
     *
439
     * Brian Cloteaux, Is This for Real? Fast Graphicality Testing,
440
     * Computing Prescriptions, pp. 91-95, vol. 17 (2015)
441
     * https://dx.doi.org/10.1109/MCSE.2015.125
442
     *
443
     * It uses counting sort to achieve linear runtime.
444
     */
445
446
0
    IGRAPH_VECTOR_INT_INIT_FINALLY(&num_degs, p);
447
448
0
    dmin = p; dmax = 0; dsum = 0; n = 0;
449
0
    for (i = 0; i < p; ++i) {
450
0
        igraph_int_t d = VECTOR(*degrees)[i];
451
452
0
        if (d < 0 || d >= p) {
453
0
            *res = false;
454
0
            goto finish;
455
0
        }
456
457
0
        if (d > 0) {
458
0
            dmax = d > dmax ? d : dmax;
459
0
            dmin = d < dmin ? d : dmin;
460
0
            dsum += d;
461
0
            n++;
462
0
            VECTOR(num_degs)[d] += 1;
463
0
        }
464
0
    }
465
466
0
    if (dsum % 2 != 0) {
467
0
        *res = false;
468
0
        goto finish;
469
0
    }
470
471
0
    if (n == 0) {
472
0
        *res = true;
473
0
        goto finish; /* all degrees are zero => graphical */
474
0
    }
475
476
    /* According to:
477
     *
478
     * G. Cairns, S. Mendan, and Y. Nikolayevsky, A sharp refinement of a result of Zverovich-Zverovich,
479
     * Discrete Math. 338, 1085 (2015).
480
     * https://dx.doi.org/10.1016/j.disc.2015.02.001
481
     *
482
     * a sufficient but not necessary condition of graphicality for a sequence of
483
     * n strictly positive integers is that
484
     *
485
     * dmin * n >= floor( (dmax + dmin + 1)^2 / 4 ) - 1
486
     * if dmin is odd or (dmax + dmin) mod 4 == 1
487
     *
488
     * or
489
     *
490
     * dmin * n >= floor( (dmax + dmin + 1)^2 / 4 )
491
     * otherwise.
492
     */
493
494
0
    zverovich_bound = ((dmax + dmin + 1) * (dmax + dmin + 1)) / 4;
495
0
    if (dmin % 2 == 1 || (dmax + dmin) % 4 == 1) {
496
0
        zverovich_bound -= 1;
497
0
    }
498
499
0
    if (dmin*n >= zverovich_bound) {
500
0
        *res = true;
501
0
        goto finish;
502
0
    }
503
504
0
    k = 0; sum_deg = 0; sum_ni = 0; sum_ini = 0;
505
0
    for (dk = dmax; dk >= dmin; --dk) {
506
0
        igraph_int_t run_size, v;
507
508
0
        if (dk < k+1) {
509
0
            *res = true;
510
0
            goto finish;
511
0
        }
512
513
0
        run_size = VECTOR(num_degs)[dk];
514
0
        if (run_size > 0) {
515
0
            if (dk < k + run_size) {
516
0
                run_size = dk - k;
517
0
            }
518
0
            sum_deg += run_size * dk;
519
0
            for (v=0; v < run_size; ++v) {
520
0
                sum_ni += VECTOR(num_degs)[k+v];
521
0
                sum_ini += (k+v) * VECTOR(num_degs)[k+v];
522
0
            }
523
0
            k += run_size;
524
0
            if (sum_deg > k*(n-1) - k*sum_ni + sum_ini) {
525
0
                *res = false;
526
0
                goto finish;
527
0
            }
528
0
        }
529
0
    }
530
531
0
    *res = true;
532
533
0
finish:
534
0
    igraph_vector_int_destroy(&num_degs);
535
0
    IGRAPH_FINALLY_CLEAN(1);
536
537
0
    return IGRAPH_SUCCESS;
538
0
}
539
540
541
/***** Directed case *****/
542
543
/* Directed loopy multigraph:
544
 *  - Degrees must be non-negative.
545
 *  - The sum of in- and out-degrees must be the same.
546
 */
547
0
static igraph_error_t igraph_i_is_graphical_directed_loopy_multi(const igraph_vector_int_t *out_degrees, const igraph_vector_int_t *in_degrees, igraph_bool_t *res) {
548
0
    igraph_int_t sumdiff; /* difference between sum of in- and out-degrees */
549
0
    igraph_int_t n = igraph_vector_int_size(out_degrees);
550
0
    igraph_int_t i;
551
552
0
    IGRAPH_ASSERT(igraph_vector_int_size(in_degrees) == n);
553
554
0
    sumdiff = 0;
555
0
    for (i = 0; i < n; ++i) {
556
0
        igraph_int_t dout = VECTOR(*out_degrees)[i];
557
0
        igraph_int_t din  = VECTOR(*in_degrees)[i];
558
559
0
        if (dout < 0 || din < 0) {
560
0
            *res = false;
561
0
            return IGRAPH_SUCCESS;
562
0
        }
563
564
0
        sumdiff += din - dout;
565
0
    }
566
567
0
    *res = sumdiff == 0;
568
569
0
    return IGRAPH_SUCCESS;
570
0
}
571
572
573
/* Directed loopless multigraph:
574
 *  - Degrees must be non-negative.
575
 *  - The sum of in- and out-degrees must be the same.
576
 *  - The sum of out-degrees must be no smaller than d_max,
577
 *    where d_max is the largest total degree.
578
 */
579
0
static igraph_error_t igraph_i_is_graphical_directed_loopless_multi(const igraph_vector_int_t *out_degrees, const igraph_vector_int_t *in_degrees, igraph_bool_t *res) {
580
0
    igraph_int_t i, sumin, sumout, dmax;
581
0
    igraph_int_t n = igraph_vector_int_size(out_degrees);
582
583
0
    IGRAPH_ASSERT(igraph_vector_int_size(in_degrees) == n);
584
585
0
    sumin = 0; sumout = 0;
586
0
    dmax = 0;
587
0
    for (i = 0; i < n; ++i) {
588
0
        igraph_int_t dout = VECTOR(*out_degrees)[i];
589
0
        igraph_int_t din  = VECTOR(*in_degrees)[i];
590
0
        igraph_int_t d = dout + din;
591
592
0
        if (dout < 0 || din < 0) {
593
0
            *res = false;
594
0
            return IGRAPH_SUCCESS;
595
0
        }
596
597
0
        sumin += din; sumout += dout;
598
599
0
        if (d > dmax) {
600
0
            dmax = d;
601
0
        }
602
0
    }
603
604
0
    *res = (sumin == sumout) && (sumout >= dmax);
605
606
0
    return IGRAPH_SUCCESS;
607
0
}
608
609
610
/* Directed graph with no multi-edges and at most one self-loop per vertex:
611
 *  - Degrees must be non-negative.
612
 *  - Equivalent to bipartite simple graph.
613
 */
614
0
static igraph_error_t igraph_i_is_graphical_directed_loopy_simple(const igraph_vector_int_t *out_degrees, const igraph_vector_int_t *in_degrees, igraph_bool_t *res) {
615
0
    return igraph_i_is_bigraphical_simple(out_degrees, in_degrees, res);
616
0
}
617
618
619
/* Directed simple graph:
620
 *  - Degrees must be non-negative.
621
 *  - The sum of in- and out-degrees must be the same.
622
 *  - Use the Fulkerson-Chen-Anstee theorem
623
 */
624
0
static igraph_error_t igraph_i_is_graphical_directed_simple(const igraph_vector_int_t *out_degrees, const igraph_vector_int_t *in_degrees, igraph_bool_t *res) {
625
0
    igraph_vector_int_t in_degree_cumcounts, in_degree_counts;
626
0
    igraph_vector_int_t sorted_in_degrees, sorted_out_degrees;
627
0
    igraph_vector_int_t left_pq, right_pq;
628
0
    igraph_int_t lhs, rhs, left_pq_size, right_pq_size, left_i, right_i, left_sum, right_sum;
629
630
    /* The conditions from the loopy multigraph case are necessary here as well. */
631
0
    IGRAPH_CHECK(igraph_i_is_graphical_directed_loopy_multi(out_degrees, in_degrees, res));
632
0
    if (! *res) {
633
0
        return IGRAPH_SUCCESS;
634
0
    }
635
636
0
    const igraph_int_t vcount = igraph_vector_int_size(out_degrees);
637
0
    if (vcount == 0) {
638
0
        *res = true;
639
0
        return IGRAPH_SUCCESS;
640
0
    }
641
642
643
0
    IGRAPH_VECTOR_INT_INIT_FINALLY(&in_degree_cumcounts, vcount+1);
644
645
    /* Compute in_degree_cumcounts[d+1] to be the no. of in-degrees == d */
646
0
    for (igraph_int_t v = 0; v < vcount; v++) {
647
0
        igraph_int_t indeg = VECTOR(*in_degrees)[v];
648
0
        igraph_int_t outdeg = VECTOR(*out_degrees)[v];
649
0
        if (indeg >= vcount || outdeg >= vcount) {
650
0
            *res = false;
651
0
            igraph_vector_int_destroy(&in_degree_cumcounts);
652
0
            IGRAPH_FINALLY_CLEAN(1);
653
0
            return IGRAPH_SUCCESS;
654
0
        }
655
0
        VECTOR(in_degree_cumcounts)[indeg + 1]++;
656
0
    }
657
658
    /* Compute in_degree_cumcounts[d] to be the no. of in-degrees < d */
659
0
    for (igraph_int_t indeg = 0; indeg < vcount; indeg++) {
660
0
        VECTOR(in_degree_cumcounts)[indeg+1] += VECTOR(in_degree_cumcounts)[indeg];
661
0
    }
662
663
0
    IGRAPH_VECTOR_INT_INIT_FINALLY(&sorted_out_degrees, vcount);
664
0
    IGRAPH_VECTOR_INT_INIT_FINALLY(&sorted_in_degrees, vcount);
665
666
    /* In the following loop, in_degree_counts[d] keeps track of the number of vertices
667
     * with in-degree d that were already placed. */
668
0
    IGRAPH_VECTOR_INT_INIT_FINALLY(&in_degree_counts, vcount);
669
670
0
    for (igraph_int_t v = 0; v < vcount; v++) {
671
0
        igraph_int_t outdeg = VECTOR(*out_degrees)[v];
672
0
        igraph_int_t indeg  = VECTOR(*in_degrees)[v];
673
0
        igraph_int_t idx = VECTOR(in_degree_cumcounts)[indeg] + VECTOR(in_degree_counts)[indeg];
674
0
        VECTOR(sorted_out_degrees)[vcount - idx - 1] = outdeg;
675
0
        VECTOR(sorted_in_degrees)[vcount - idx - 1] = indeg;
676
0
        VECTOR(in_degree_counts)[indeg]++;
677
0
    }
678
679
0
    igraph_vector_int_destroy(&in_degree_counts);
680
0
    igraph_vector_int_destroy(&in_degree_cumcounts);
681
0
    IGRAPH_FINALLY_CLEAN(2);
682
683
    /* Be optimistic, then check whether the Fulkerson–Chen–Anstee condition
684
     * holds for every k. In particular, for every k in [0; n), it must be true
685
     * that:
686
     *
687
     * \sum_{i=0}^k indegree[i] <=
688
     *     \sum_{i=0}^k min(outdegree[i], k) +
689
     *     \sum_{i=k+1}^{n-1} min(outdegree[i], k + 1)
690
     */
691
692
0
#define INDEGREE(x) (VECTOR(sorted_in_degrees)[x])
693
0
#define OUTDEGREE(x) (VECTOR(sorted_out_degrees)[x])
694
695
0
    IGRAPH_VECTOR_INT_INIT_FINALLY(&left_pq, vcount);
696
0
    IGRAPH_VECTOR_INT_INIT_FINALLY(&right_pq, vcount);
697
698
0
    left_pq_size = 0;
699
0
    right_pq_size = vcount;
700
0
    left_i = 0;
701
0
    right_i = 0;
702
0
    left_sum = 0;
703
0
    right_sum = 0;
704
0
    for (igraph_int_t i = 0; i < vcount; i++) {
705
0
        VECTOR(right_pq)[OUTDEGREE(i)]++;
706
0
    }
707
708
0
    *res = true;
709
0
    lhs = 0;
710
0
    rhs = 0;
711
0
    for (igraph_int_t i = 0; i < vcount; i++) {
712
0
        lhs += INDEGREE(i);
713
714
        /* It is enough to check for indexes where the in-degree is about to
715
         * decrease in the next step; see "Stronger condition" in the Wikipedia
716
         * entry for the Fulkerson-Chen-Anstee condition. However, this does not
717
         * provide any noticeable benefits for the current implementation. */
718
719
0
        if (OUTDEGREE(i) < i) {
720
0
            left_sum += OUTDEGREE(i);
721
0
        }
722
0
        else {
723
0
            VECTOR(left_pq)[OUTDEGREE(i)]++;
724
0
            left_pq_size++;
725
0
        }
726
0
        while (left_i < i) {
727
0
            while (VECTOR(left_pq)[left_i] > 0) {
728
0
                VECTOR(left_pq)[left_i]--;
729
0
                left_pq_size--;
730
0
                left_sum += left_i;
731
0
            }
732
0
            left_i++;
733
0
        }
734
735
0
        while (right_i < i + 1) {
736
0
            while (VECTOR(right_pq)[right_i] > 0) {
737
0
                VECTOR(right_pq)[right_i]--;
738
0
                right_pq_size--;
739
0
                right_sum += right_i;
740
0
            }
741
0
            right_i++;
742
0
        }
743
0
        if (OUTDEGREE(i) < i + 1) {
744
0
            right_sum -= OUTDEGREE(i);
745
0
        }
746
0
        else {
747
0
            VECTOR(right_pq)[OUTDEGREE(i)]--;
748
0
            right_pq_size--;
749
0
        }
750
751
0
        rhs = left_sum + i * left_pq_size + right_sum + (i + 1) * right_pq_size;
752
0
        if (lhs > rhs) {
753
0
            *res = false;
754
0
            break;
755
0
        }
756
0
    }
757
758
0
#undef INDEGREE
759
0
#undef OUTDEGREE
760
761
0
    igraph_vector_int_destroy(&sorted_in_degrees);
762
0
    igraph_vector_int_destroy(&sorted_out_degrees);
763
0
    igraph_vector_int_destroy(&left_pq);
764
0
    igraph_vector_int_destroy(&right_pq);
765
0
    IGRAPH_FINALLY_CLEAN(4);
766
767
0
    return IGRAPH_SUCCESS;
768
0
}
769
770
771
772
/***** Bipartite case *****/
773
774
/* Bipartite graph with multi-edges:
775
 *  - Degrees must be non-negative.
776
 *  - Sum of degrees must be the same in the two partitions.
777
 */
778
0
static igraph_error_t igraph_i_is_bigraphical_multi(const igraph_vector_int_t *degrees1, const igraph_vector_int_t *degrees2, igraph_bool_t *res) {
779
0
    igraph_int_t i;
780
0
    igraph_int_t sum1, sum2;
781
0
    igraph_int_t n1 = igraph_vector_int_size(degrees1), n2 = igraph_vector_int_size(degrees2);
782
783
0
    sum1 = 0;
784
0
    for (i = 0; i < n1; ++i) {
785
0
        igraph_int_t d = VECTOR(*degrees1)[i];
786
787
0
        if (d < 0) {
788
0
            *res = false;
789
0
            return IGRAPH_SUCCESS;
790
0
        }
791
792
0
        sum1 += d;
793
0
    }
794
795
0
    sum2 = 0;
796
0
    for (i = 0; i < n2; ++i) {
797
0
        igraph_int_t d = VECTOR(*degrees2)[i];
798
799
0
        if (d < 0) {
800
0
            *res = false;
801
0
            return IGRAPH_SUCCESS;
802
0
        }
803
804
0
        sum2 += d;
805
0
    }
806
807
0
    *res = (sum1 == sum2);
808
809
0
    return IGRAPH_SUCCESS;
810
0
}
811
812
813
/* Bipartite simple graph:
814
 *  - Degrees must be non-negative.
815
 *  - Sum of degrees must be the same in the two partitions.
816
 *  - Use the Gale-Ryser theorem.
817
 */
818
0
static igraph_error_t igraph_i_is_bigraphical_simple(const igraph_vector_int_t *degrees1, const igraph_vector_int_t *degrees2, igraph_bool_t *res) {
819
0
    igraph_int_t n1 = igraph_vector_int_size(degrees1), n2 = igraph_vector_int_size(degrees2);
820
0
    igraph_vector_int_t deg_freq1, deg_freq2;
821
0
    igraph_int_t lhs_sum, partial_rhs_sum, partial_rhs_count;
822
0
    igraph_int_t a, b, k;
823
824
0
    if (n1 == 0 && n2 == 0) {
825
0
        *res = true;
826
0
        return IGRAPH_SUCCESS;
827
0
    }
828
829
    /* The conditions from the multigraph case are necessary here as well. */
830
0
    IGRAPH_CHECK(igraph_i_is_bigraphical_multi(degrees1, degrees2, res));
831
0
    if (! *res) {
832
0
        return IGRAPH_SUCCESS;
833
0
    }
834
835
    /* Ensure that degrees1 is the shorter vector as a minor optimization: */
836
0
    if (n2 < n1) {
837
0
        const igraph_vector_int_t *tmp;
838
0
        igraph_int_t n;
839
840
0
        tmp = degrees1;
841
0
        degrees1 = degrees2;
842
0
        degrees2 = tmp;
843
844
0
        n = n1;
845
0
        n1 = n2;
846
0
        n2 = n;
847
0
    }
848
849
    /* Counting sort the degrees. */
850
    /* Since the graph is simple, a vertex's degree can be at most the size of the other partition. */
851
0
    IGRAPH_VECTOR_INT_INIT_FINALLY(&deg_freq1, n2+1);
852
0
    IGRAPH_VECTOR_INT_INIT_FINALLY(&deg_freq2, n1+1);
853
854
0
    for (igraph_int_t i = 0; i < n1; ++i) {
855
0
        igraph_int_t degree = VECTOR(*degrees1)[i];
856
0
        if (degree > n2) {
857
0
            *res = false;
858
0
            goto bigraphical_simple_done;
859
0
        }
860
0
        ++VECTOR(deg_freq1)[degree];
861
0
    }
862
0
    for (igraph_int_t i = 0; i < n2; ++i) {
863
0
        igraph_int_t degree = VECTOR(*degrees2)[i];
864
0
        if (degree > n1) {
865
0
            *res = false;
866
0
            goto bigraphical_simple_done;
867
0
        }
868
0
        ++VECTOR(deg_freq2)[degree];
869
0
    }
870
871
    /*
872
     * We follow the description of the Gale-Ryser theorem in:
873
     *
874
     * A. Berger, A note on the characterization of digraphic sequences, Discrete Math. 314, 38 (2014).
875
     * https://doi.org/10.1016/j.disc.2013.09.010
876
     *
877
     * Gale-Ryser condition with 0-based indexing:
878
     *
879
     * a_i and b_i denote the degree sequences of the two partitions.
880
     *
881
     * Assuming that a_0 >= a_1 >= ... >= a_{n_1 - 1},
882
     *
883
     * \sum_{i=0}^k a_i <= \sum_{j=0}^{n_2} min(b_i, k+1)
884
     *
885
     * for all 0 <= k < n_1.
886
     *
887
     * Additionally, based on Theorem 3 in [Berger 2014], it is sufficient to do the check
888
     * for k such that a_k > a_{k+1} and for k=(n_1-1).
889
     */
890
891
    /* While this formulation does not require sorting degree2,
892
     * doing so allows for a linear-time incremental computation
893
     * of the inequality's right-hand-side.
894
     */
895
896
0
    *res = true; /* be optimistic */
897
0
    lhs_sum = 0;
898
0
    partial_rhs_sum = 0; /* the sum of those elements in the rhs which are <= (k+1) */
899
0
    partial_rhs_count = 0; /* number of elements in the rhs which are <= (k+1) */
900
0
    b = 0; /* index in deg_freq2 */
901
0
    k = -1;
902
0
    for (a = n2; a >= 0; --a) {
903
0
        igraph_int_t acount = VECTOR(deg_freq1)[a];
904
0
        lhs_sum += a * acount;
905
0
        k += acount;
906
907
0
        while (b <= k + 1) {
908
0
            igraph_int_t bcount = VECTOR(deg_freq2)[b];
909
0
            partial_rhs_sum += b * bcount;
910
0
            partial_rhs_count += bcount;
911
912
0
            ++b;
913
0
        }
914
915
        /* rhs_sum for a given k is partial_rhs_sum + (n2 - partial_rhs_count) * (k+1) */
916
0
        if (lhs_sum > partial_rhs_sum + (n2 - partial_rhs_count) * (k+1) ) {
917
0
            *res = false;
918
0
            break;
919
0
        }
920
0
    }
921
922
0
bigraphical_simple_done:
923
0
    igraph_vector_int_destroy(&deg_freq1);
924
0
    igraph_vector_int_destroy(&deg_freq2);
925
0
    IGRAPH_FINALLY_CLEAN(2);
926
927
0
    return IGRAPH_SUCCESS;
928
0
}