1 /*
2   Constructing realizations of degree sequences and bi-degree sequences.
3   Copyright (C) 2018-2020 Szabolcs Horvat <szhorvat@gmail.com>
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, write to the Free Software
17   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18   02110-1301 USA
19 */
20 
21 #include "igraph_constructors.h"
22 #include "igraph_interface.h"
23 
24 #include <vector>
25 #include <list>
26 #include <algorithm>
27 #include <utility>
28 
29 
30 // (vertex, degree) pair
31 struct vd_pair {
32     long vertex;
33     igraph_integer_t degree;
34 
vd_pairvd_pair35     vd_pair(long vertex, igraph_integer_t degree) : vertex(vertex), degree(degree) {}
36 };
37 
38 // (indegree, outdegree)
39 typedef std::pair<igraph_integer_t, igraph_integer_t> bidegree;
40 
41 // (vertex, bidegree) pair
42 struct vbd_pair {
43     long vertex;
44     bidegree degree;
45 
vbd_pairvbd_pair46     vbd_pair(long vertex, bidegree degree) : vertex(vertex), degree(degree) {}
47 };
48 
49 // Comparison function for vertex-degree pairs.
50 // Also used for lexicographic sorting of bi-degrees.
degree_greater(const T & a,const T & b)51 template<typename T> inline bool degree_greater(const T &a, const T &b) {
52     return a.degree > b.degree;
53 }
54 
degree_less(const T & a,const T & b)55 template<typename T> inline bool degree_less(const T &a, const T &b) {
56     return a.degree < b.degree;
57 }
58 
59 
60 // Generate undirected realization as edge-list.
61 // If largest=true, always choose the vertex with the largest remaining degree to connect up next.
62 // Otherwise, always choose the one with the smallest remaining degree.
igraph_i_havel_hakimi(const igraph_vector_t * deg,igraph_vector_t * edges,bool largest)63 static int igraph_i_havel_hakimi(const igraph_vector_t *deg, igraph_vector_t *edges, bool largest) {
64     long n = igraph_vector_size(deg);
65 
66     long ec = 0; // number of edges added so far
67 
68     std::vector<vd_pair> vertices;
69     vertices.reserve(n);
70     for (int i = 0; i < n; ++i) {
71         vertices.push_back(vd_pair(i, VECTOR(*deg)[i]));
72     }
73 
74     while (! vertices.empty()) {
75         if (largest) {
76             std::stable_sort(vertices.begin(), vertices.end(), degree_less<vd_pair>);
77         } else {
78             std::stable_sort(vertices.begin(), vertices.end(), degree_greater<vd_pair>);
79         }
80 
81         // take the next vertex to be connected up
82         vd_pair vd = vertices.back();
83         vertices.pop_back();
84 
85         if (vd.degree == 0) {
86             continue;
87         }
88 
89         if (vertices.size() < size_t(vd.degree)) {
90             goto fail;
91         }
92 
93         if (largest) {
94             for (int i = 0; i < vd.degree; ++i) {
95                 if (--(vertices[vertices.size() - 1 - i].degree) < 0) {
96                     goto fail;
97                 }
98 
99                 VECTOR(*edges)[2 * (ec + i)] = vd.vertex;
100                 VECTOR(*edges)[2 * (ec + i) + 1] = vertices[vertices.size() - 1 - i].vertex;
101             }
102         } else {
103             // this loop can only be reached if all zero-degree nodes have already been removed
104             // therefore decrementing remaining degrees is safe
105             for (int i = 0; i < vd.degree; ++i) {
106                 vertices[i].degree--;
107 
108                 VECTOR(*edges)[2 * (ec + i)] = vd.vertex;
109                 VECTOR(*edges)[2 * (ec + i) + 1] = vertices[i].vertex;
110             }
111         }
112 
113         ec += vd.degree;
114     }
115 
116     return IGRAPH_SUCCESS;
117 
118 fail:
119     IGRAPH_ERROR("The given degree sequence is not realizable", IGRAPH_EINVAL);
120 }
121 
122 
123 // Choose vertices in the order of their IDs.
igraph_i_havel_hakimi_index(const igraph_vector_t * deg,igraph_vector_t * edges)124 static int igraph_i_havel_hakimi_index(const igraph_vector_t *deg, igraph_vector_t *edges) {
125     long n = igraph_vector_size(deg);
126 
127     long ec = 0; // number of edges added so far
128 
129     typedef std::list<vd_pair> vlist;
130     vlist vertices;
131     for (int i = 0; i < n; ++i) {
132         vertices.push_back(vd_pair(i, VECTOR(*deg)[i]));
133     }
134 
135     std::vector<vlist::iterator> pointers;
136     pointers.reserve(n);
137     for (vlist::iterator it = vertices.begin(); it != vertices.end(); ++it) {
138         pointers.push_back(it);
139     }
140 
141     for (std::vector<vlist::iterator>::iterator pt = pointers.begin(); pt != pointers.end(); ++pt) {
142         vertices.sort(degree_greater<vd_pair>);
143 
144         vd_pair vd = **pt;
145         vertices.erase(*pt);
146 
147         if (vd.degree == 0) {
148             continue;
149         }
150 
151         int k;
152         vlist::iterator it;
153         for (it = vertices.begin(), k = 0;
154              k != vd.degree && it != vertices.end();
155              ++it, ++k) {
156             if (--(it->degree) < 0) {
157                 goto fail;
158             }
159 
160             VECTOR(*edges)[2 * (ec + k)] = vd.vertex;
161             VECTOR(*edges)[2 * (ec + k) + 1] = it->vertex;
162         }
163         if (it == vertices.end() && k < vd.degree) {
164             goto fail;
165         }
166 
167         ec += vd.degree;
168     }
169 
170     return IGRAPH_SUCCESS;
171 
172 fail:
173     IGRAPH_ERROR("The given degree sequence is not realizable", IGRAPH_EINVAL);
174 }
175 
176 
is_nonzero_outdeg(const vbd_pair & vd)177 inline bool is_nonzero_outdeg(const vbd_pair &vd) {
178     return (vd.degree.second != 0);
179 }
180 
181 
182 // The below implementations of the Kleitman-Wang algorithm follow the description in https://arxiv.org/abs/0905.4913
183 
184 // Realize bi-degree sequence as edge list
185 // If smallest=true, always choose the vertex with "smallest" bi-degree for connecting up next,
186 // otherwise choose the "largest" (based on lexicographic bi-degree ordering).
igraph_i_kleitman_wang(const igraph_vector_t * outdeg,const igraph_vector_t * indeg,igraph_vector_t * edges,bool smallest)187 static int igraph_i_kleitman_wang(const igraph_vector_t *outdeg, const igraph_vector_t *indeg, igraph_vector_t *edges, bool smallest) {
188     long n = igraph_vector_size(indeg); // number of vertices
189 
190     long ec = 0; // number of edges added so far
191 
192     std::vector<vbd_pair> vertices;
193     vertices.reserve(n);
194     for (int i = 0; i < n; ++i) {
195         vertices.push_back(vbd_pair(i, bidegree(VECTOR(*indeg)[i], VECTOR(*outdeg)[i])));
196     }
197 
198     while (true) {
199         // sort vertices by (in, out) degree pairs in decreasing order
200         std::stable_sort(vertices.begin(), vertices.end(), degree_greater<vbd_pair>);
201 
202         // remove (0,0)-degree vertices
203         while (!vertices.empty() && vertices.back().degree == bidegree(0, 0)) {
204             vertices.pop_back();
205         }
206 
207         // if no vertices remain, stop
208         if (vertices.empty()) {
209             break;
210         }
211 
212         // choose a vertex the out-stubs of which will be connected
213         vbd_pair *vdp;
214         if (smallest) {
215             vdp = &*std::find_if(vertices.rbegin(), vertices.rend(), is_nonzero_outdeg);
216         } else {
217             vdp = &*std::find_if(vertices.begin(), vertices.end(), is_nonzero_outdeg);
218         }
219 
220 
221         // are there a sufficient number of other vertices to connect to?
222         if (vertices.size() - 1 < vdp->degree.second) {
223             goto fail;
224         }
225 
226         // create the connections
227         int k = 0;
228         for (std::vector<vbd_pair>::iterator it = vertices.begin();
229              k < vdp->degree.second;
230              ++it) {
231             if (it->vertex == vdp->vertex) {
232                 continue;    // do not create a self-loop
233             }
234             if (--(it->degree.first) < 0) {
235                 goto fail;
236             }
237 
238             VECTOR(*edges)[2 * (ec + k)] = vdp->vertex;
239             VECTOR(*edges)[2 * (ec + k) + 1] = it->vertex;
240 
241             k++;
242         }
243 
244         ec += vdp->degree.second;
245         vdp->degree.second = 0;
246     }
247 
248     return IGRAPH_SUCCESS;
249 
250 fail:
251     IGRAPH_ERROR("The given directed degree sequence is not realizable", IGRAPH_EINVAL);
252 }
253 
254 
255 // Choose vertices in the order of their IDs.
igraph_i_kleitman_wang_index(const igraph_vector_t * outdeg,const igraph_vector_t * indeg,igraph_vector_t * edges)256 static int igraph_i_kleitman_wang_index(const igraph_vector_t *outdeg, const igraph_vector_t *indeg, igraph_vector_t *edges) {
257     long n = igraph_vector_size(indeg); // number of vertices
258 
259     long ec = 0; // number of edges added so far
260 
261     typedef std::list<vbd_pair> vlist;
262     vlist vertices;
263     for (int i = 0; i < n; ++i) {
264         vertices.push_back(vbd_pair(i, bidegree(VECTOR(*indeg)[i], VECTOR(*outdeg)[i])));
265     }
266 
267     std::vector<vlist::iterator> pointers;
268     pointers.reserve(n);
269     for (vlist::iterator it = vertices.begin(); it != vertices.end(); ++it) {
270         pointers.push_back(it);
271     }
272 
273     for (std::vector<vlist::iterator>::iterator pt = pointers.begin(); pt != pointers.end(); ++pt) {
274         // sort vertices by (in, out) degree pairs in decreasing order
275         // note: std::list::sort does a stable sort
276         vertices.sort(degree_greater<vbd_pair>);
277 
278         // choose a vertex the out-stubs of which will be connected
279         vbd_pair &vd = **pt;
280 
281         if (vd.degree.second == 0) {
282             continue;
283         }
284 
285         int k = 0;
286         vlist::iterator it;
287         for (it = vertices.begin();
288              k != vd.degree.second && it != vertices.end();
289              ++it) {
290             if (it->vertex == vd.vertex) {
291                 continue;
292             }
293 
294             if (--(it->degree.first) < 0) {
295                 goto fail;
296             }
297 
298             VECTOR(*edges)[2 * (ec + k)] = vd.vertex;
299             VECTOR(*edges)[2 * (ec + k) + 1] = it->vertex;
300 
301             ++k;
302         }
303         if (it == vertices.end() && k < vd.degree.second) {
304             goto fail;
305         }
306 
307         ec += vd.degree.second;
308         vd.degree.second = 0;
309     }
310 
311     return IGRAPH_SUCCESS;
312 
313 fail:
314     IGRAPH_ERROR("The given directed degree sequence is not realizable", IGRAPH_EINVAL);
315 }
316 
317 
igraph_i_realize_undirected_degree_sequence(igraph_t * graph,const igraph_vector_t * deg,igraph_realize_degseq_t method)318 static int igraph_i_realize_undirected_degree_sequence(
319     igraph_t *graph,
320     const igraph_vector_t *deg,
321     igraph_realize_degseq_t method) {
322     long node_count = igraph_vector_size(deg);
323     long deg_sum = long(igraph_vector_sum(deg));
324 
325     if (deg_sum % 2 != 0) {
326         IGRAPH_ERROR("The sum of degrees must be even for an undirected graph", IGRAPH_EINVAL);
327     }
328 
329     if (igraph_vector_min(deg) < 0) {
330         IGRAPH_ERROR("Vertex degrees must be non-negative", IGRAPH_EINVAL);
331     }
332 
333     igraph_vector_t edges;
334     IGRAPH_CHECK(igraph_vector_init(&edges, deg_sum));
335     IGRAPH_FINALLY(igraph_vector_destroy, &edges);
336 
337     switch (method) {
338     case IGRAPH_REALIZE_DEGSEQ_SMALLEST:
339         IGRAPH_CHECK(igraph_i_havel_hakimi(deg, &edges, false));
340         break;
341     case IGRAPH_REALIZE_DEGSEQ_LARGEST:
342         IGRAPH_CHECK(igraph_i_havel_hakimi(deg, &edges, true));
343         break;
344     case IGRAPH_REALIZE_DEGSEQ_INDEX:
345         IGRAPH_CHECK(igraph_i_havel_hakimi_index(deg, &edges));
346         break;
347     default:
348         IGRAPH_ERROR("Invalid degree sequence realization method", IGRAPH_EINVAL);
349     }
350 
351     igraph_create(graph, &edges, igraph_integer_t(node_count), false);
352 
353     igraph_vector_destroy(&edges);
354     IGRAPH_FINALLY_CLEAN(1);
355 
356     return IGRAPH_SUCCESS;
357 }
358 
359 
igraph_i_realize_directed_degree_sequence(igraph_t * graph,const igraph_vector_t * outdeg,const igraph_vector_t * indeg,igraph_realize_degseq_t method)360 static int igraph_i_realize_directed_degree_sequence(
361     igraph_t *graph,
362     const igraph_vector_t *outdeg,
363     const igraph_vector_t *indeg,
364     igraph_realize_degseq_t method) {
365     long node_count = igraph_vector_size(outdeg);
366     long edge_count = long(igraph_vector_sum(outdeg));
367 
368     if (igraph_vector_size(indeg) != node_count) {
369         IGRAPH_ERROR("In- and out-degree sequences must have the same length", IGRAPH_EINVAL);
370     }
371     if (igraph_vector_sum(indeg) != edge_count) {
372         IGRAPH_ERROR("In- and out-degree sequences do not sum to the same value", IGRAPH_EINVAL);
373     }
374 
375     if (igraph_vector_min(outdeg) < 0 || igraph_vector_min(indeg) < 0) {
376         IGRAPH_ERROR("Vertex degrees must be non-negative", IGRAPH_EINVAL);
377     }
378 
379     igraph_vector_t edges;
380     IGRAPH_CHECK(igraph_vector_init(&edges, 2 * edge_count));
381     IGRAPH_FINALLY(igraph_vector_destroy, &edges);
382 
383     switch (method) {
384     case IGRAPH_REALIZE_DEGSEQ_SMALLEST:
385         IGRAPH_CHECK(igraph_i_kleitman_wang(outdeg, indeg, &edges, true));
386         break;
387     case IGRAPH_REALIZE_DEGSEQ_LARGEST:
388         IGRAPH_CHECK(igraph_i_kleitman_wang(outdeg, indeg, &edges, false));
389         break;
390     case IGRAPH_REALIZE_DEGSEQ_INDEX:
391         IGRAPH_CHECK(igraph_i_kleitman_wang_index(outdeg, indeg, &edges));
392         break;
393     default:
394         IGRAPH_ERROR("Invalid bi-degree sequence realization method", IGRAPH_EINVAL);
395     }
396 
397     igraph_create(graph, &edges, igraph_integer_t(node_count), true);
398 
399     igraph_vector_destroy(&edges);
400     IGRAPH_FINALLY_CLEAN(1);
401 
402     return IGRAPH_SUCCESS;
403 }
404 
405 
406 /**
407  * \ingroup generators
408  * \function igraph_realize_degree_sequence
409  * \brief Generates a graph with the given degree sequence
410  *
411  * This function constructs a simple graph that realizes the given degree sequence
412  * using the Havel-Hakimi algorithm, or the given (directed) out- and in-degree
413  * sequences using the related Kleitman-Wang algorithm.
414  *
415  * The algorithms work by choosing an arbitrary vertex and connecting all its stubs
416  * to other vertices of highest degree.  In the directed case, the "highest" (in, out) degree
417  * pairs are determined based on lexicographic ordering.
418  *
419  * The \c method parameter controls the order in which the vertices to be connected are chosen.
420  *
421  * </para><para>
422  * References:
423  *
424  * </para><para>
425  * V. Havel,
426  * Poznámka o existenci konečných grafů (A remark on the existence of finite graphs),
427  * Časopis pro pěstování matematiky 80, 477-480 (1955).
428  * http://eudml.org/doc/19050
429  *
430  * </para><para>
431  * S. L. Hakimi,
432  * On Realizability of a Set of Integers as Degrees of the Vertices of a Linear Graph,
433  * Journal of the SIAM 10, 3 (1962).
434  * https://www.jstor.org/stable/2098746
435  *
436  * </para><para>
437  * D. J. Kleitman and D. L. Wang,
438  * Algorithms for Constructing Graphs and Digraphs with Given Valences and Factors,
439  * Discrete Mathematics 6, 1 (1973).
440  * https://doi.org/10.1016/0012-365X%2873%2990037-X
441  *
442  * </para><para>
443  * Sz. Horvát and C. D. Modes,
444  * Connectivity matters: Construction and exact random sampling of connected graphs (2020).
445  * https://arxiv.org/abs/2009.03747
446  *
447  * \param graph Pointer to an uninitialized graph object.
448  * \param outdeg The degree sequence for a simple undirected graph
449  *        (if \p indeg is NULL or of length zero), or the out-degree sequence of
450  *        a directed graph (if \p indeg is of nonzero size).
451  * \param indeg It is either a zero-length vector or \c NULL (if an undirected graph
452  *        is generated), or the in-degree sequence.
453  * \param method The method to generate the graph. Possible values:
454  *        \clist
455  *          \cli IGRAPH_REALIZE_DEGSEQ_SMALLEST
456  *          The vertex with smallest remaining degree is selected first. The result is usually
457  *          a graph with high negative degree assortativity. In the undirected case, this method
458  *          is guaranteed to generate a connected graph, provided that a connected realization exists.
459  *          See Horvát and Modes (2020) as well as http://szhorvat.net/pelican/hh-connected-graphs.html
460  *          for a proof. In the directed case it tends to generate weakly connected graphs, but this is not
461  *          guaranteed.
462  *          \cli IGRAPH_REALIZE_DEGSEQ_LARGEST
463  *          The vertex with the largest remaining degree is selected first. The result
464  *          is usually a graph with high positive degree assortativity, and is often disconnected.
465  *          \cli IGRAPH_REALIZE_DEGSEQ_INDEX
466  *          The vertices are selected in order of their index (i.e. their position in the degree vector).
467  *          Note that sorting the degree vector and using the \c INDEX method is not equivalent
468  *          to the \c SMALLEST method above, as \c SMALLEST uses the smallest \em remaining
469  *          degree for selecting vertices, not the smallest \em initial degree.
470  *         \endclist
471  * \return Error code:
472  *          \clist
473  *          \cli IGRAPH_ENOMEM
474  *           There is not enough memory to perform the operation.
475  *          \cli IGRAPH_EINVAL
476  *           Invalid method parameter, or invalid in- and/or out-degree vectors.
477  *           The degree vectors should be non-negative, the length
478  *           and sum of \p outdeg and \p indeg should match for directed graphs.
479  *          \endclist
480  *
481  * \sa  \ref igraph_is_graphical_degree_sequence()
482  *      \ref igraph_degree_sequence_game()
483  *      \ref igraph_k_regular_game()
484  *      \ref igraph_rewire()
485  *
486  */
487 
igraph_realize_degree_sequence(igraph_t * graph,const igraph_vector_t * outdeg,const igraph_vector_t * indeg,igraph_realize_degseq_t method)488 int igraph_realize_degree_sequence(
489     igraph_t *graph,
490     const igraph_vector_t *outdeg, const igraph_vector_t *indeg,
491     igraph_realize_degseq_t method) {
492     long n = igraph_vector_size(outdeg);
493     if (n != igraph_integer_t(n)) { // does the vector size fit into an igraph_integer_t ?
494         IGRAPH_ERROR("Degree sequence vector too long", IGRAPH_EINVAL);
495     }
496 
497     bool directed = bool(indeg) && igraph_vector_size(indeg) != 0;
498 
499     try {
500         if (directed) {
501             return igraph_i_realize_directed_degree_sequence(graph, outdeg, indeg, method);
502         } else {
503             return igraph_i_realize_undirected_degree_sequence(graph, outdeg, method);
504         }
505     } catch (const std::bad_alloc &) {
506         IGRAPH_ERROR("Cannot realize degree sequence due to insufficient memory", IGRAPH_ENOMEM);
507     }
508 }
509