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