/src/igraph/src/misc/degree_sequence.cpp
Line | Count | Source |
1 | | /* |
2 | | igraph library. |
3 | | Constructing realizations of degree sequences and bi-degree sequences. |
4 | | Copyright (C) 2018-2024 The igraph development team <igraph@igraph.org> |
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, see <https://www.gnu.org/licenses/>. |
18 | | */ |
19 | | |
20 | | #include "igraph_constructors.h" |
21 | | |
22 | | #include "core/exceptions.h" |
23 | | #include "math/safe_intop.h" |
24 | | #include "misc/graphicality.h" |
25 | | |
26 | | #include <vector> |
27 | | #include <list> |
28 | | #include <algorithm> |
29 | | #include <utility> |
30 | | #include <stack> |
31 | | #include <cassert> |
32 | | |
33 | | /******************************/ |
34 | | /***** Helper constructs ******/ |
35 | | /******************************/ |
36 | | |
37 | | // (vertex, degree) pair |
38 | | struct vd_pair { |
39 | | igraph_int_t vertex; |
40 | | igraph_int_t degree; |
41 | | |
42 | | vd_pair() = default; |
43 | 10.9M | vd_pair(igraph_int_t vertex, igraph_int_t degree) : vertex(vertex), degree(degree) {} |
44 | | }; |
45 | | |
46 | | // (indegree, outdegree) |
47 | | typedef std::pair<igraph_int_t, igraph_int_t> bidegree; |
48 | | |
49 | | // (vertex, bidegree) pair |
50 | | struct vbd_pair { |
51 | | igraph_int_t vertex; |
52 | | bidegree degree; |
53 | | |
54 | 951k | vbd_pair(igraph_int_t vertex, bidegree degree) : vertex(vertex), degree(degree) {} |
55 | | }; |
56 | | |
57 | | // Comparison function for vertex-degree pairs. |
58 | | // Also used for lexicographic sorting of bi-degrees. |
59 | 1.58G | template<typename T> inline bool degree_greater(const T &a, const T &b) { |
60 | 1.58G | return a.degree > b.degree; |
61 | 1.58G | } bool degree_greater<vbd_pair>(vbd_pair const&, vbd_pair const&) Line | Count | Source | 59 | 260M | template<typename T> inline bool degree_greater(const T &a, const T &b) { | 60 | 260M | return a.degree > b.degree; | 61 | 260M | } |
bool degree_greater<vd_pair>(vd_pair const&, vd_pair const&) Line | Count | Source | 59 | 1.32G | template<typename T> inline bool degree_greater(const T &a, const T &b) { | 60 | 1.32G | return a.degree > b.degree; | 61 | 1.32G | } |
|
62 | | |
63 | | template<typename T> inline bool degree_less(const T &a, const T &b) { |
64 | | return a.degree < b.degree; |
65 | | } |
66 | | |
67 | | |
68 | | /*************************************/ |
69 | | /***** Undirected simple graphs ******/ |
70 | | /*************************************/ |
71 | | |
72 | | // "Bucket Node" for nodes of the same degree |
73 | | struct BNode { |
74 | | igraph_int_t count = 0; |
75 | | std::stack<vd_pair> nodes; |
76 | | igraph_int_t next; // next bucket (higher degree) |
77 | | igraph_int_t prev; // prev bucket (lower degree) |
78 | | |
79 | 1.99M | bool is_empty() const { return count == 0; } |
80 | | }; |
81 | | |
82 | | struct HavelHakimiList { |
83 | | igraph_int_t n_buckets; // no of buckets, INCLUDING sentinels |
84 | | std::vector<BNode> buckets; |
85 | | |
86 | | // Given degree sequence, sets up linked list of BNodes (degree buckets) |
87 | | // sentinel BNode [0] and [N] as bookends |
88 | | // O(N) |
89 | | explicit HavelHakimiList(const igraph_vector_int_t *degseq) : |
90 | 7.36k | n_buckets(igraph_vector_int_size(degseq)+1), buckets(n_buckets) |
91 | 7.36k | { |
92 | 7.36k | igraph_int_t n_nodes = igraph_vector_int_size(degseq); |
93 | 1.38M | for (igraph_int_t i = 0; i <= n_nodes; i++) { |
94 | 1.37M | if (i == 0) { |
95 | 7.36k | buckets[i].prev = -1; |
96 | 1.36M | } else { |
97 | 1.36M | buckets[i].prev = i - 1; |
98 | 1.36M | } |
99 | | |
100 | 1.37M | if (i == n_nodes) { |
101 | 7.36k | buckets[i].next = -1; |
102 | 1.36M | } else { |
103 | 1.36M | buckets[i].next = i + 1; |
104 | 1.36M | } |
105 | 1.37M | } |
106 | | |
107 | 1.37M | for (igraph_int_t i = 0; i < n_nodes; i++) { |
108 | 1.36M | igraph_int_t degree = VECTOR(*degseq)[i]; |
109 | 1.36M | buckets[degree].nodes.push(vd_pair{i, degree}); |
110 | 1.36M | buckets[degree].count++; |
111 | 1.36M | } |
112 | 7.36k | } |
113 | | |
114 | | // ----- O(1) convenience functions ----- // |
115 | 162k | const BNode & head() const { return buckets.front(); } |
116 | 0 | const BNode & tail() const { return buckets.back(); } |
117 | | |
118 | | // gets the largest non-empty bucket below 'degree', |
119 | | // or 0 if one does not exist |
120 | 701k | igraph_int_t get_prev(igraph_int_t degree) { |
121 | 701k | assert(0 < degree && degree <= n_buckets - 1); // upper sentinel allowed as input |
122 | 701k | igraph_int_t curr = buckets[degree].prev; |
123 | 2.06M | while (curr > 0 && buckets[curr].is_empty()) { |
124 | 1.36M | remove_bucket(curr); |
125 | 1.36M | curr = buckets[degree].prev; |
126 | 1.36M | } |
127 | 701k | return curr; |
128 | 701k | } |
129 | | |
130 | | // returns max degree non-empty bucket, |
131 | | // or 0 (sentinel) if all buckets are empty |
132 | 560k | igraph_int_t get_max_bucket() { |
133 | | // TODO: either change get_prev to take a BNode, or change |
134 | | // head()/tail() to return integers |
135 | 560k | return get_prev(n_buckets - 1); |
136 | 560k | } |
137 | | |
138 | | // returns min degree non-empty bucket, |
139 | | // or n_buckets - 1 (sentinel) if all buckets are empty |
140 | 134k | igraph_int_t get_min_bucket() { |
141 | 134k | igraph_int_t curr = head().next; |
142 | 162k | while (curr < n_buckets - 1 && buckets[curr].is_empty()) { |
143 | 27.9k | remove_bucket(curr); |
144 | 27.9k | curr = head().next; |
145 | 27.9k | } |
146 | 134k | return curr; |
147 | 134k | } |
148 | | |
149 | 1.39M | void remove_bucket(igraph_int_t degree) { |
150 | | // bounds check and prevent accidental removal of sentinels |
151 | 1.39M | assert(0 < degree && degree < n_buckets - 1); |
152 | | |
153 | 1.39M | igraph_int_t &prev_idx = buckets[degree].prev; |
154 | 1.39M | igraph_int_t &next_idx = buckets[degree].next; |
155 | 1.39M | if (prev_idx != -1) buckets[prev_idx].next = next_idx; |
156 | 1.39M | if (next_idx != -1) buckets[next_idx].prev = prev_idx; |
157 | | |
158 | 1.39M | prev_idx = -1; |
159 | 1.39M | next_idx = -1; |
160 | 1.39M | } |
161 | | |
162 | 215k | void insert_bucket(igraph_int_t degree) { |
163 | 215k | assert(0 <= degree && degree < n_buckets - 1); // can insert into zero-degree bucket |
164 | | |
165 | 215k | igraph_int_t &prev_idx = buckets[degree].prev; |
166 | 215k | igraph_int_t &next_idx = buckets[degree].next; |
167 | | |
168 | 215k | if (prev_idx == -1 && next_idx == -1) { |
169 | 35.3k | next_idx = degree + 1; |
170 | 35.3k | prev_idx = buckets[next_idx].prev; |
171 | | |
172 | 35.3k | buckets[next_idx].prev = degree; |
173 | 35.3k | buckets[prev_idx].next = degree; |
174 | 35.3k | } |
175 | 215k | } |
176 | | |
177 | 215k | void insert_node(vd_pair node) { |
178 | 215k | insert_bucket(node.degree); // does nothing if already exists |
179 | 215k | buckets[node.degree].nodes.push(vd_pair{node.vertex, node.degree}); |
180 | 215k | buckets[node.degree].count++; |
181 | 215k | } |
182 | | |
183 | 30.3k | bool get_max_node(vd_pair &max_node) { |
184 | 30.3k | igraph_int_t max_bucket = get_max_bucket(); |
185 | 30.3k | if (max_bucket <= 0) { |
186 | 2.02k | return false; |
187 | 2.02k | } |
188 | 28.2k | max_node = buckets[max_bucket].nodes.top(); |
189 | 28.2k | return true; |
190 | 30.3k | } |
191 | | |
192 | 28.2k | void remove_max_node() { |
193 | 28.2k | igraph_int_t max_bucket = get_max_bucket(); |
194 | 28.2k | if (max_bucket <= 0) return; |
195 | 28.2k | buckets[max_bucket].nodes.pop(); |
196 | 28.2k | buckets[max_bucket].count--; |
197 | 28.2k | } |
198 | | |
199 | 68.0k | bool get_min_node(vd_pair &min_node) { |
200 | 68.0k | igraph_int_t min_bucket = get_min_bucket(); |
201 | 68.0k | if (min_bucket >= n_buckets - 1) { |
202 | 2.02k | return false; |
203 | 2.02k | } |
204 | 65.9k | min_node = buckets[min_bucket].nodes.top(); |
205 | 65.9k | return true; |
206 | 68.0k | } |
207 | | |
208 | 65.9k | void remove_min_node() { |
209 | 65.9k | igraph_int_t min_bucket = get_min_bucket(); |
210 | 65.9k | if (min_bucket >= n_buckets - 1) return; |
211 | 65.9k | buckets[min_bucket].nodes.pop(); |
212 | 65.9k | buckets[min_bucket].count--; |
213 | 65.9k | } |
214 | | |
215 | | // Given degree of selected "hub" node, returns degree many "spoke" nodes to connect to |
216 | | // amortized O(alpha(n)) |
217 | | igraph_error_t get_spokes(igraph_int_t degree, const igraph_vector_int_t &seq, |
218 | 502k | igraph_vector_int_t &spokes) { |
219 | 502k | std::stack<igraph_int_t> buckets_req; // stack of needed degree buckets |
220 | 502k | igraph_int_t num_nodes = 0; |
221 | 502k | igraph_int_t curr = get_max_bucket(); // starts with max_bucket |
222 | | |
223 | 502k | igraph_vector_int_clear(&spokes); |
224 | 502k | IGRAPH_CHECK(igraph_vector_int_reserve(&spokes, degree)); |
225 | | |
226 | 643k | while (num_nodes < degree && curr > 0) { |
227 | 140k | num_nodes += buckets[curr].count; |
228 | 140k | buckets_req.push(curr); |
229 | 140k | curr = get_prev(curr); // gets next smallest NON-EMPTY bucket |
230 | 140k | } |
231 | 502k | if (num_nodes < degree) { // not enough spokes for hub degree |
232 | 1.29k | IGRAPH_ERROR("The given degree sequence cannot be realized as a simple graph.", IGRAPH_EINVAL); |
233 | 1.29k | } |
234 | | |
235 | 500k | igraph_int_t num_skip = num_nodes - degree; |
236 | 640k | while (!buckets_req.empty()) { // starting from the smallest degree |
237 | 139k | igraph_int_t bucket = buckets_req.top(); |
238 | 139k | buckets_req.pop(); |
239 | | |
240 | 139k | igraph_int_t to_get = buckets[bucket].count - num_skip; |
241 | 361k | while (to_get > 0) { |
242 | 221k | vd_pair node = buckets[bucket].nodes.top(); |
243 | 221k | if (VECTOR(seq)[node.vertex] != 0) { // if "not marked for removal" |
244 | 215k | IGRAPH_CHECK(igraph_vector_int_push_back(&spokes, node.vertex)); // add as spoke |
245 | | |
246 | 215k | node.degree--; |
247 | 215k | insert_node(node); // first, insert into bucket below |
248 | | |
249 | 215k | buckets[bucket].count--; |
250 | 215k | to_get--; |
251 | 215k | } |
252 | 221k | buckets[bucket].nodes.pop(); // then pop from original bucket |
253 | 221k | } |
254 | 139k | num_skip = 0; |
255 | 139k | } |
256 | 500k | return IGRAPH_SUCCESS; |
257 | 500k | } |
258 | | }; |
259 | | |
260 | | /* |
261 | | * This implementation works by grouping nodes by their remaining "stubs" (degrees) into an |
262 | | * array of "degree buckets" (see struct HavelHakimiList above) - i.e. each bucket holds |
263 | | * all nodes with that degree. The array runs from index 0 to index N, with 0 and N as |
264 | | * sentinel buckets (since any graphical sequence for a simple graph will not have degrees |
265 | | * greater than N - 1, and nodes with degree 0 can be ignored). Thus, only O(V) time is |
266 | | * needed to allocate each node to its starting degree bucket, after which no re-sorting is |
267 | | * done - if a node is used up as a "hub", it is simply removed (or lazy deleted if not |
268 | | * immediately accessible), and if it is chosen as a "spoke", it will only shift down one |
269 | | * bucket (constant operation). |
270 | | * |
271 | | * Additionally, each degree bucket keeps track of its next largest and next smallest degree |
272 | | * bucket via their index in the array. This makes it very time efficient to find the |
273 | | * largest and/or smallest nodes as needed for both "hub" and "spoke" nodes (specifically, |
274 | | * amortized near-constant time). |
275 | | * |
276 | | * Below is an example run-through using the degree sequence [3, 2, 3, 1, 1] and the |
277 | | * IGRAPH_REALIZE_DEGSEQ_SMALLEST method: |
278 | | * |
279 | | * Initial list |
280 | | * [0][1][2][3][4][5] <- degree buckets |
281 | | * 4 1 2 <- node ID (index in the degseq) in each bucket |
282 | | * 3 0 |
283 | | * |
284 | | * By the smallest first method, we must first choose a node with the smallest degree to |
285 | | * serve as the "hub". get_min_node() retrieves a node from bucket [1], which sentinel [0] |
286 | | * indicates as its next largest non-empty bucket in its .next field. |
287 | | * [0][1][2][3][4][5] 4 <- retrieved "hub" node. It is removed from the bucket |
288 | | * 3 1 2 |
289 | | * 0 |
290 | | * |
291 | | * Node 4 has degree 1, therefore we need to select 1 "spoke" node to connect it to. Using |
292 | | * get_max_node(), we go to retrieve a node from bucket [4], which sentinel [5] indicates |
293 | | * as its next smallest non-empty bucket. Finding [4] empty, the bucket is removed, and |
294 | | * node 2 is finally retrieved from bucket [3]. |
295 | | * [0][1][2][3][5] 4-2 <- an edge is formed between them |
296 | | * 3 1 0 |
297 | | * |
298 | | * After one "stub"/degree of node 2 is used up, it gets shifted down one bucket. |
299 | | * [0][1][2][3][5] 4-2 |
300 | | * 3 2 0 |
301 | | * 1 |
302 | | * |
303 | | * The process repeats. We look at the smallest degree bucket for a "hub" and remove it. |
304 | | * [0][1][2][3][5] 4-2 |
305 | | * 2 0 3 |
306 | | * 1 |
307 | | * |
308 | | * Node 3 has degree 1, so we pick 1 "spoke", and replace it into the bucket below. |
309 | | * [0][1][2][3][5] 4-2 |
310 | | * 0 3-0 |
311 | | * 2 |
312 | | * 1 |
313 | | * |
314 | | * The process continues until we are unable to find either a non-zero "hub" node |
315 | | * (algorithm complete and sequence is graphical) or enough non-zero "spoke" nodes once a |
316 | | * hub has been selected (sequence is non-graphical). |
317 | | */ |
318 | | static igraph_error_t igraph_i_havel_hakimi(const igraph_vector_int_t *degseq, |
319 | | igraph_vector_int_t *edges, |
320 | 7.92k | igraph_realize_degseq_t method) { |
321 | 7.92k | igraph_int_t n_nodes = igraph_vector_int_size(degseq); |
322 | | |
323 | | // ----- upfront error/graphicality checks ----- // |
324 | 7.92k | if (n_nodes == 0 || (n_nodes == 1 && VECTOR(*degseq)[0] == 0)) { |
325 | 12 | return IGRAPH_SUCCESS; |
326 | 12 | } |
327 | | |
328 | 1.37M | for (igraph_int_t i = 0; i < n_nodes; i++) { |
329 | 1.36M | igraph_int_t deg = VECTOR(*degseq)[i]; |
330 | 1.36M | if (deg >= n_nodes) { |
331 | 555 | IGRAPH_ERROR("The given degree sequence cannot be realized as a simple graph.", IGRAPH_EINVAL); |
332 | 555 | } |
333 | 1.36M | } |
334 | | |
335 | | // ----- main Havel-Hakimi loop ----- // |
336 | | // O(V + alpha(V) * E) |
337 | | // O(V + E) for the LARGEST_FIRST method |
338 | 7.36k | igraph_vector_int_t seq; |
339 | 7.36k | IGRAPH_CHECK(igraph_vector_int_init_copy(&seq, degseq)); |
340 | 7.36k | IGRAPH_FINALLY(igraph_vector_int_destroy, &seq); |
341 | | |
342 | 7.36k | HavelHakimiList vault(&seq); |
343 | | |
344 | 7.36k | igraph_int_t n_edges_added = 0; |
345 | 7.36k | igraph_vector_int_t spokes; |
346 | 7.36k | IGRAPH_VECTOR_INT_INIT_FINALLY(&spokes, 0); |
347 | | |
348 | 508k | for (igraph_int_t i = 0; i < n_nodes; i++) { |
349 | | // hub node selection |
350 | 506k | vd_pair hub; |
351 | 506k | if (method == IGRAPH_REALIZE_DEGSEQ_SMALLEST) { |
352 | 68.0k | if (!vault.get_min_node(/* out param */hub)) break; |
353 | 65.9k | vault.remove_min_node(); |
354 | 65.9k | } |
355 | 438k | else if (method == IGRAPH_REALIZE_DEGSEQ_LARGEST) { |
356 | 30.3k | if (!vault.get_max_node(/* out param */hub)) break; |
357 | 28.2k | vault.remove_max_node(); |
358 | 28.2k | } |
359 | 408k | else if (method == IGRAPH_REALIZE_DEGSEQ_INDEX) { |
360 | 408k | igraph_int_t degree = VECTOR(seq)[i]; |
361 | 408k | hub = vd_pair{i, degree}; |
362 | 408k | vault.buckets[degree].count--; |
363 | 408k | } else { |
364 | | // The fatal error is effectively an assertion that this line |
365 | | // should not be reachable: |
366 | 0 | IGRAPH_FATAL("Invalid degree sequence realization method."); |
367 | 0 | } |
368 | 502k | VECTOR(seq)[hub.vertex] = 0; |
369 | | |
370 | | // spoke nodes selection |
371 | 502k | IGRAPH_CHECK(vault.get_spokes(hub.degree, seq, spokes)); |
372 | | |
373 | 500k | igraph_int_t n_spokes = igraph_vector_int_size(&spokes); |
374 | 716k | for (igraph_int_t j = 0; j < n_spokes; j++) { |
375 | 215k | igraph_int_t spoke_idx = VECTOR(spokes)[j]; |
376 | 215k | VECTOR(*edges)[2*n_edges_added] = hub.vertex; |
377 | 215k | VECTOR(*edges)[2*n_edges_added + 1] = spoke_idx; |
378 | 215k | n_edges_added++; |
379 | | |
380 | 215k | VECTOR(seq)[spoke_idx]--; |
381 | 215k | } |
382 | 500k | } |
383 | 6.06k | igraph_vector_int_destroy(&spokes); |
384 | 6.06k | igraph_vector_int_destroy(&seq); |
385 | 6.06k | IGRAPH_FINALLY_CLEAN(2); |
386 | 6.06k | return IGRAPH_SUCCESS; |
387 | 7.36k | } |
388 | | |
389 | | /***********************************/ |
390 | | /***** Undirected multigraphs ******/ |
391 | | /***********************************/ |
392 | | |
393 | | // Given a sequence that is sorted, except for its first element, |
394 | | // move the first element to the correct position fully sort the sequence. |
395 | | template<typename It, typename Compare> |
396 | 631k | static void bubble_up(It first, It last, Compare comp) { |
397 | 631k | if (first == last) { |
398 | 0 | return; |
399 | 0 | } |
400 | 631k | It it = first; |
401 | 631k | it++; |
402 | 19.6M | while (it != last) { |
403 | 19.3M | if (comp(*first, *it)) { |
404 | 334k | break; |
405 | 18.9M | } else { |
406 | 18.9M | std::swap(*first, *it); |
407 | 18.9M | } |
408 | 18.9M | first = it; |
409 | 18.9M | it++; |
410 | 18.9M | } |
411 | 631k | } |
412 | | |
413 | | // In each step, choose a vertex (the largest degree one if largest=true, |
414 | | // the smallest degree one otherwise) and connect it to the largest remaining degree vertex. |
415 | | // This will create a connected loopless multigraph, if one exists. |
416 | | // If loops=true, and a loopless multigraph does not exist, complete the procedure |
417 | | // by adding loops on the last vertex. |
418 | | // If largest=false, and the degree sequence was potentially connected, the resulting |
419 | | // graph will be connected. |
420 | | // O(V * E + V log V) |
421 | 12.8k | static igraph_error_t igraph_i_realize_undirected_multi(const igraph_vector_int_t *deg, igraph_vector_int_t *edges, bool loops, bool largest) { |
422 | 12.8k | igraph_int_t vcount = igraph_vector_int_size(deg); |
423 | | |
424 | 12.8k | if (vcount == 0) { |
425 | 8 | return IGRAPH_SUCCESS; |
426 | 8 | } |
427 | | |
428 | 12.8k | std::vector<vd_pair> vertices; |
429 | 12.8k | vertices.reserve(vcount); |
430 | 2.21M | for (igraph_int_t i = 0; i < vcount; ++i) { |
431 | 2.20M | igraph_int_t d = VECTOR(*deg)[i]; |
432 | 2.20M | vertices.push_back(vd_pair(i, d)); |
433 | 2.20M | } |
434 | | |
435 | | // Initial sort in non-increasing order. |
436 | | // O (V log V) |
437 | 12.8k | std::stable_sort(vertices.begin(), vertices.end(), degree_greater<vd_pair>); |
438 | | |
439 | 12.8k | igraph_int_t ec = 0; |
440 | 2.63M | while (! vertices.empty()) { |
441 | | // Remove any zero degrees, and error on negative ones. |
442 | | |
443 | 2.62M | vd_pair &w = vertices.back(); |
444 | | |
445 | 2.62M | if (w.degree == 0) { |
446 | 2.20M | vertices.pop_back(); |
447 | 2.20M | continue; |
448 | 2.20M | } |
449 | | |
450 | | // If only one vertex remains, then the degree sequence cannot be realized as |
451 | | // a loopless multigraph. We either complete the graph by adding loops on this vertex |
452 | | // or throw an error, depending on the 'loops' setting. |
453 | 423k | if (vertices.size() == 1) { |
454 | 2.04k | if (loops) { |
455 | 41.4k | for (igraph_int_t i = 0; i < w.degree / 2; ++i) { |
456 | 40.1k | VECTOR(*edges)[2 * ec] = w.vertex; |
457 | 40.1k | VECTOR(*edges)[2 * ec + 1] = w.vertex; |
458 | 40.1k | ec++; |
459 | 40.1k | } |
460 | 1.36k | break; |
461 | 1.36k | } else { |
462 | 682 | IGRAPH_ERROR("The given degree sequence cannot be realized as a loopless multigraph.", IGRAPH_EINVAL); |
463 | 682 | } |
464 | 2.04k | } |
465 | | |
466 | | // At this point we are guaranteed to have at least two remaining vertices. |
467 | | |
468 | 421k | vd_pair *u, *v; |
469 | 421k | if (largest) { |
470 | 210k | u = &vertices[0]; |
471 | 210k | v = &vertices[1]; |
472 | 210k | } else { |
473 | 210k | u = &vertices.front(); |
474 | 210k | v = &vertices.back(); |
475 | 210k | } |
476 | | |
477 | 421k | u->degree -= 1; |
478 | 421k | v->degree -= 1; |
479 | | |
480 | 421k | VECTOR(*edges)[2*ec] = u->vertex; |
481 | 421k | VECTOR(*edges)[2*ec+1] = v->vertex; |
482 | 421k | ec++; |
483 | | |
484 | | // Now the first element may be out of order. |
485 | | // If largest=true, the first two elements may be out of order. |
486 | | // Restore the sorted order using a single step of bubble sort. |
487 | 421k | if (largest) { |
488 | 210k | bubble_up(vertices.begin() + 1, vertices.end(), degree_greater<vd_pair>); |
489 | 210k | } |
490 | 421k | bubble_up(vertices.begin(), vertices.end(), degree_greater<vd_pair>); |
491 | 421k | } |
492 | | |
493 | 12.1k | return IGRAPH_SUCCESS; |
494 | 12.8k | } |
495 | | |
496 | | // O(V * E + V log V) |
497 | 6.41k | static igraph_error_t igraph_i_realize_undirected_multi_index(const igraph_vector_int_t *deg, igraph_vector_int_t *edges, bool loops) { |
498 | 6.41k | igraph_int_t vcount = igraph_vector_int_size(deg); |
499 | | |
500 | 6.41k | if (vcount == 0) { |
501 | 4 | return IGRAPH_SUCCESS; |
502 | 4 | } |
503 | | |
504 | 6.41k | typedef std::list<vd_pair> vlist; |
505 | 6.41k | vlist vertices; |
506 | 1.10M | for (igraph_int_t i = 0; i < vcount; ++i) { |
507 | 1.10M | vertices.push_back(vd_pair(i, VECTOR(*deg)[i])); |
508 | 1.10M | } |
509 | | |
510 | 6.41k | std::vector<vlist::iterator> pointers; |
511 | 6.41k | pointers.reserve(vcount); |
512 | 1.10M | for (auto it = vertices.begin(); it != vertices.end(); ++it) { |
513 | 1.10M | pointers.push_back(it); |
514 | 1.10M | } |
515 | | |
516 | | // Initial sort |
517 | 6.41k | vertices.sort(degree_greater<vd_pair>); |
518 | | |
519 | 6.41k | igraph_int_t ec = 0; |
520 | 1.02M | for (const auto &pt : pointers) { |
521 | 1.02M | vd_pair vd = *pt; |
522 | 1.02M | vertices.erase(pt); |
523 | | |
524 | 1.23M | while (vd.degree > 0) { |
525 | 211k | auto uit = vertices.begin(); |
526 | | |
527 | 211k | if (vertices.empty() || uit->degree == 0) { |
528 | | // We are out of non-zero degree vertices to connect to. |
529 | 1.02k | if (loops) { |
530 | 20.7k | for (igraph_int_t i = 0; i < vd.degree / 2; ++i) { |
531 | 20.0k | VECTOR(*edges)[2 * ec] = vd.vertex; |
532 | 20.0k | VECTOR(*edges)[2 * ec + 1] = vd.vertex; |
533 | 20.0k | ec++; |
534 | 20.0k | } |
535 | 682 | return IGRAPH_SUCCESS; |
536 | 682 | } else { |
537 | 341 | IGRAPH_ERROR("The given degree sequence cannot be realized as a loopless multigraph.", IGRAPH_EINVAL); |
538 | 341 | } |
539 | 1.02k | } |
540 | | |
541 | 210k | vd.degree -= 1; |
542 | 210k | uit->degree -= 1; |
543 | | |
544 | 210k | VECTOR(*edges)[2*ec] = vd.vertex; |
545 | 210k | VECTOR(*edges)[2*ec+1] = uit->vertex; |
546 | 210k | ec++; |
547 | | |
548 | | // If there are at least two elements, and the first two are not in order, |
549 | | // re-sort the list. A possible optimization would be a version of |
550 | | // bubble_up() that can exchange list nodes instead of swapping their values. |
551 | 210k | if (vertices.size() > 1) { |
552 | 205k | auto wit = uit; |
553 | 205k | ++wit; |
554 | | |
555 | 205k | if (wit->degree > uit->degree) { |
556 | 137k | vertices.sort(degree_greater<vd_pair>); |
557 | 137k | } |
558 | 205k | } |
559 | 210k | } |
560 | 1.02M | } |
561 | | |
562 | 5.39k | return IGRAPH_SUCCESS; |
563 | 6.41k | } |
564 | | |
565 | | |
566 | | /***********************************/ |
567 | | /***** Directed simple graphs ******/ |
568 | | /***********************************/ |
569 | | |
570 | 576k | inline bool is_nonzero_outdeg(const vbd_pair &vd) { |
571 | 576k | return (vd.degree.second != 0); |
572 | 576k | } |
573 | | |
574 | | |
575 | | // The below implementations of the Kleitman-Wang algorithm follow the description in https://arxiv.org/abs/0905.4913 |
576 | | |
577 | | // Realize bi-degree sequence as edge list |
578 | | // If smallest=true, always choose the vertex with "smallest" bi-degree for connecting up next, |
579 | | // otherwise choose the "largest" (based on lexicographic bi-degree ordering). |
580 | | // O(E + V^2 log V) |
581 | 3.67k | static igraph_error_t igraph_i_kleitman_wang(const igraph_vector_int_t *outdeg, const igraph_vector_int_t *indeg, igraph_vector_int_t *edges, bool smallest) { |
582 | 3.67k | igraph_int_t n = igraph_vector_int_size(indeg); // number of vertices |
583 | | |
584 | 3.67k | igraph_int_t ec = 0; // number of edges added so far |
585 | | |
586 | 3.67k | std::vector<vbd_pair> vertices; |
587 | 3.67k | vertices.reserve(n); |
588 | 638k | for (igraph_int_t i = 0; i < n; ++i) { |
589 | 634k | vertices.push_back(vbd_pair(i, bidegree(VECTOR(*indeg)[i], VECTOR(*outdeg)[i]))); |
590 | 634k | } |
591 | | |
592 | 72.2k | while (true) { |
593 | | // sort vertices by (in, out) degree pairs in decreasing order |
594 | | // O(V log V) |
595 | 72.2k | std::stable_sort(vertices.begin(), vertices.end(), degree_greater<vbd_pair>); |
596 | | |
597 | | // remove (0,0)-degree vertices |
598 | 700k | while (!vertices.empty() && vertices.back().degree == bidegree(0, 0)) { |
599 | 627k | vertices.pop_back(); |
600 | 627k | } |
601 | | |
602 | | // if no vertices remain, stop |
603 | 72.2k | if (vertices.empty()) { |
604 | 2.56k | break; |
605 | 2.56k | } |
606 | | |
607 | | // choose a vertex the out-stubs of which will be connected |
608 | | // note: a vertex with non-zero out-degree is guaranteed to exist |
609 | | // because there are _some_ non-zero degrees and the sum of in- and out-degrees |
610 | | // is the same |
611 | 69.6k | vbd_pair *vdp; |
612 | | // O(V) |
613 | 69.6k | if (smallest) { |
614 | 35.7k | vdp = &*std::find_if(vertices.rbegin(), vertices.rend(), is_nonzero_outdeg); |
615 | 35.7k | } else { |
616 | 33.9k | vdp = &*std::find_if(vertices.begin(), vertices.end(), is_nonzero_outdeg); |
617 | 33.9k | } |
618 | | |
619 | | // are there a sufficient number of other vertices to connect to? |
620 | 69.6k | if (static_cast<igraph_int_t>(vertices.size()) - 1 < vdp->degree.second) { |
621 | 992 | goto fail; |
622 | 992 | } |
623 | | |
624 | | // create the connections |
625 | 68.6k | igraph_int_t k = 0; |
626 | 68.6k | for (auto it = vertices.begin(); |
627 | 175k | k < vdp->degree.second; |
628 | 106k | ++it) { |
629 | 106k | if (it->vertex == vdp->vertex) { |
630 | 11.4k | continue; // do not create a self-loop |
631 | 11.4k | } |
632 | 95.2k | if (--(it->degree.first) < 0) { |
633 | 118 | goto fail; |
634 | 118 | } |
635 | | |
636 | 95.1k | VECTOR(*edges)[2 * (ec + k)] = vdp->vertex; |
637 | 95.1k | VECTOR(*edges)[2 * (ec + k) + 1] = it->vertex; |
638 | | |
639 | 95.1k | k++; |
640 | 95.1k | } |
641 | | |
642 | 68.5k | ec += vdp->degree.second; |
643 | 68.5k | vdp->degree.second = 0; |
644 | 68.5k | } |
645 | | |
646 | 2.56k | return IGRAPH_SUCCESS; |
647 | | |
648 | 1.11k | fail: |
649 | 1.11k | IGRAPH_ERROR("The given directed degree sequences cannot be realized as a simple graph.", IGRAPH_EINVAL); |
650 | 1.11k | } |
651 | | |
652 | | |
653 | | // Choose vertices in the order of their IDs. |
654 | | // O(E + V^2 log V) |
655 | 1.83k | static igraph_error_t igraph_i_kleitman_wang_index(const igraph_vector_int_t *outdeg, const igraph_vector_int_t *indeg, igraph_vector_int_t *edges) { |
656 | 1.83k | igraph_int_t n = igraph_vector_int_size(indeg); // number of vertices |
657 | | |
658 | 1.83k | igraph_int_t ec = 0; // number of edges added so far |
659 | | |
660 | 1.83k | typedef std::list<vbd_pair> vlist; |
661 | 1.83k | vlist vertices; |
662 | 319k | for (igraph_int_t i = 0; i < n; ++i) { |
663 | 317k | vertices.push_back(vbd_pair(i, bidegree(VECTOR(*indeg)[i], VECTOR(*outdeg)[i]))); |
664 | 317k | } |
665 | | |
666 | 1.83k | std::vector<vlist::iterator> pointers; |
667 | 1.83k | pointers.reserve(n); |
668 | 319k | for (auto it = vertices.begin(); it != vertices.end(); ++it) { |
669 | 317k | pointers.push_back(it); |
670 | 317k | } |
671 | | |
672 | 268k | for (const auto &pt : pointers) { |
673 | | // sort vertices by (in, out) degree pairs in decreasing order |
674 | | // note: std::list::sort does a stable sort |
675 | 268k | vertices.sort(degree_greater<vbd_pair>); |
676 | | |
677 | | // choose a vertex the out-stubs of which will be connected |
678 | 268k | vbd_pair &vd = *pt; |
679 | | |
680 | 268k | if (vd.degree.second == 0) { |
681 | 234k | continue; |
682 | 234k | } |
683 | | |
684 | 34.7k | igraph_int_t k = 0; |
685 | 34.7k | vlist::iterator it; |
686 | 34.7k | for (it = vertices.begin(); |
687 | 85.3k | k != vd.degree.second && it != vertices.end(); |
688 | 51.0k | ++it) { |
689 | 51.0k | if (it->vertex == vd.vertex) { |
690 | 1.67k | continue; |
691 | 1.67k | } |
692 | | |
693 | 49.4k | if (--(it->degree.first) < 0) { |
694 | 434 | goto fail; |
695 | 434 | } |
696 | | |
697 | 48.9k | VECTOR(*edges)[2 * (ec + k)] = vd.vertex; |
698 | 48.9k | VECTOR(*edges)[2 * (ec + k) + 1] = it->vertex; |
699 | | |
700 | 48.9k | ++k; |
701 | 48.9k | } |
702 | 34.2k | if (it == vertices.end() && k < vd.degree.second) { |
703 | 121 | goto fail; |
704 | 121 | } |
705 | | |
706 | 34.1k | ec += vd.degree.second; |
707 | 34.1k | vd.degree.second = 0; |
708 | 34.1k | } |
709 | | |
710 | 1.28k | return IGRAPH_SUCCESS; |
711 | | |
712 | 555 | fail: |
713 | 555 | IGRAPH_ERROR("The given directed degree sequences cannot be realized as a simple graph.", IGRAPH_EINVAL); |
714 | 555 | } |
715 | | |
716 | | |
717 | | /**************************/ |
718 | | /***** Main functions *****/ |
719 | | /**************************/ |
720 | | |
721 | | static igraph_error_t igraph_i_realize_undirected_degree_sequence( |
722 | | igraph_t *graph, |
723 | | const igraph_vector_int_t *deg, |
724 | | igraph_edge_type_sw_t allowed_edge_types, |
725 | 27.1k | igraph_realize_degseq_t method) { |
726 | 27.1k | igraph_int_t node_count = igraph_vector_int_size(deg); |
727 | 27.1k | igraph_int_t deg_sum; |
728 | | |
729 | 27.1k | IGRAPH_CHECK(igraph_i_safe_vector_int_sum(deg, °_sum)); |
730 | | |
731 | 27.1k | if (deg_sum % 2 != 0) { |
732 | 0 | IGRAPH_ERROR("The sum of degrees must be even for an undirected graph.", IGRAPH_EINVAL); |
733 | 0 | } |
734 | | |
735 | 27.1k | if (node_count > 0 && igraph_vector_int_min(deg) < 0) { |
736 | 0 | IGRAPH_ERROR("Vertex degrees must be non-negative.", IGRAPH_EINVAL); |
737 | 0 | } |
738 | | |
739 | 27.1k | igraph_vector_int_t edges; |
740 | 27.1k | IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, deg_sum); |
741 | | |
742 | 27.1k | IGRAPH_HANDLE_EXCEPTIONS_BEGIN; |
743 | 27.1k | if ( (allowed_edge_types & IGRAPH_LOOPS_SW) && (allowed_edge_types & IGRAPH_I_MULTI_EDGES_SW) && (allowed_edge_types & IGRAPH_I_MULTI_LOOPS_SW ) ) |
744 | 10.6k | { |
745 | 10.6k | switch (method) { |
746 | 3.55k | case IGRAPH_REALIZE_DEGSEQ_SMALLEST: |
747 | 3.55k | IGRAPH_CHECK(igraph_i_realize_undirected_multi(deg, &edges, true, false)); |
748 | 3.55k | break; |
749 | 3.55k | case IGRAPH_REALIZE_DEGSEQ_LARGEST: |
750 | 3.55k | IGRAPH_CHECK(igraph_i_realize_undirected_multi(deg, &edges, true, true)); |
751 | 3.55k | break; |
752 | 3.55k | case IGRAPH_REALIZE_DEGSEQ_INDEX: |
753 | 3.55k | IGRAPH_CHECK(igraph_i_realize_undirected_multi_index(deg, &edges, true)); |
754 | 3.55k | break; |
755 | 3.55k | default: |
756 | 0 | IGRAPH_ERROR("Invalid degree sequence realization method.", IGRAPH_EINVAL); |
757 | 10.6k | } |
758 | 10.6k | } |
759 | 16.5k | else if ( ! (allowed_edge_types & IGRAPH_LOOPS_SW) && (allowed_edge_types & IGRAPH_I_MULTI_EDGES_SW) ) |
760 | 8.58k | { |
761 | 8.58k | switch (method) { |
762 | 2.86k | case IGRAPH_REALIZE_DEGSEQ_SMALLEST: |
763 | 2.86k | IGRAPH_CHECK(igraph_i_realize_undirected_multi(deg, &edges, false, false)); |
764 | 2.51k | break; |
765 | 2.86k | case IGRAPH_REALIZE_DEGSEQ_LARGEST: |
766 | 2.86k | IGRAPH_CHECK(igraph_i_realize_undirected_multi(deg, &edges, false, true)); |
767 | 2.51k | break; |
768 | 2.86k | case IGRAPH_REALIZE_DEGSEQ_INDEX: |
769 | 2.86k | IGRAPH_CHECK(igraph_i_realize_undirected_multi_index(deg, &edges, false)); |
770 | 2.51k | break; |
771 | 2.51k | default: |
772 | 0 | IGRAPH_ERROR("Invalid degree sequence realization method.", IGRAPH_EINVAL); |
773 | 8.58k | } |
774 | 8.58k | } |
775 | 7.92k | else if ( (allowed_edge_types & IGRAPH_LOOPS_SW) && ! (allowed_edge_types & IGRAPH_I_MULTI_LOOPS_SW) && ! (allowed_edge_types & IGRAPH_I_MULTI_EDGES_SW) ) |
776 | 0 | { |
777 | 0 | IGRAPH_ERROR("Graph realization with at most one self-loop per vertex is not implemented.", IGRAPH_UNIMPLEMENTED); |
778 | 0 | } |
779 | 7.92k | else if ( ! (allowed_edge_types & IGRAPH_LOOPS_SW) && ! (allowed_edge_types & IGRAPH_I_MULTI_EDGES_SW) ) |
780 | 7.92k | { |
781 | 7.92k | switch (method) { |
782 | 2.64k | case IGRAPH_REALIZE_DEGSEQ_SMALLEST: |
783 | 2.64k | IGRAPH_CHECK(igraph_i_havel_hakimi(deg, &edges, IGRAPH_REALIZE_DEGSEQ_SMALLEST)); |
784 | 2.02k | break; |
785 | 2.64k | case IGRAPH_REALIZE_DEGSEQ_LARGEST: |
786 | 2.64k | IGRAPH_CHECK(igraph_i_havel_hakimi(deg, &edges, IGRAPH_REALIZE_DEGSEQ_LARGEST)); |
787 | 2.02k | break; |
788 | 2.64k | case IGRAPH_REALIZE_DEGSEQ_INDEX: |
789 | 2.64k | IGRAPH_CHECK(igraph_i_havel_hakimi(deg, &edges, IGRAPH_REALIZE_DEGSEQ_INDEX)); |
790 | 2.02k | break; |
791 | 2.02k | default: |
792 | 0 | IGRAPH_ERROR("Invalid degree sequence realization method.", IGRAPH_EINVAL); |
793 | 7.92k | } |
794 | 7.92k | } |
795 | 0 | else |
796 | 0 | { |
797 | | /* Remaining cases: |
798 | | * - At most one self-loop per vertex but multi-edges between distinct vertices allowed. |
799 | | * - At most one edge between distinct vertices but multi-self-loops allowed. |
800 | | * These cases cannot currently be requested through the documented API, |
801 | | * so no explanatory error message for now. */ |
802 | 0 | return IGRAPH_UNIMPLEMENTED; |
803 | 0 | } |
804 | 27.1k | IGRAPH_HANDLE_EXCEPTIONS_END; |
805 | | |
806 | 24.3k | IGRAPH_CHECK(igraph_create(graph, &edges, node_count, false)); |
807 | | |
808 | 24.3k | igraph_vector_int_destroy(&edges); |
809 | 24.3k | IGRAPH_FINALLY_CLEAN(1); |
810 | | |
811 | 24.3k | return IGRAPH_SUCCESS; |
812 | 24.3k | } |
813 | | |
814 | | |
815 | | static igraph_error_t igraph_i_realize_directed_degree_sequence( |
816 | | igraph_t *graph, |
817 | | const igraph_vector_int_t *outdeg, |
818 | | const igraph_vector_int_t *indeg, |
819 | | igraph_edge_type_sw_t allowed_edge_types, |
820 | 5.50k | igraph_realize_degseq_t method) { |
821 | 5.50k | igraph_int_t node_count = igraph_vector_int_size(outdeg); |
822 | 5.50k | igraph_int_t edge_count, edge_count2, indeg_sum; |
823 | | |
824 | 5.50k | IGRAPH_CHECK(igraph_i_safe_vector_int_sum(outdeg, &edge_count)); |
825 | | |
826 | 5.50k | if (igraph_vector_int_size(indeg) != node_count) { |
827 | 0 | IGRAPH_ERROR("In- and out-degree sequences must have the same length.", IGRAPH_EINVAL); |
828 | 0 | } |
829 | | |
830 | 5.50k | IGRAPH_CHECK(igraph_i_safe_vector_int_sum(indeg, &indeg_sum)); |
831 | 5.50k | if (indeg_sum != edge_count) { |
832 | 0 | IGRAPH_ERROR("In- and out-degree sequences do not sum to the same value.", IGRAPH_EINVAL); |
833 | 0 | } |
834 | | |
835 | 5.50k | if (node_count > 0 && (igraph_vector_int_min(outdeg) < 0 || igraph_vector_int_min(indeg) < 0)) { |
836 | 0 | IGRAPH_ERROR("Vertex degrees must be non-negative.", IGRAPH_EINVAL); |
837 | 0 | } |
838 | | |
839 | | /* TODO implement loopless and loopy multigraph case */ |
840 | 5.50k | if (allowed_edge_types != IGRAPH_SIMPLE_SW) { |
841 | 0 | IGRAPH_ERROR("Realizing directed degree sequences as non-simple graphs is not implemented.", IGRAPH_UNIMPLEMENTED); |
842 | 0 | } |
843 | | |
844 | 5.50k | igraph_vector_int_t edges; |
845 | 5.50k | IGRAPH_SAFE_MULT(edge_count, 2, &edge_count2); |
846 | 5.50k | IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, edge_count2); |
847 | | |
848 | 5.50k | IGRAPH_HANDLE_EXCEPTIONS_BEGIN; |
849 | 5.50k | switch (method) { |
850 | 1.83k | case IGRAPH_REALIZE_DEGSEQ_SMALLEST: |
851 | 1.83k | IGRAPH_CHECK(igraph_i_kleitman_wang(outdeg, indeg, &edges, true)); |
852 | 1.28k | break; |
853 | 1.83k | case IGRAPH_REALIZE_DEGSEQ_LARGEST: |
854 | 1.83k | IGRAPH_CHECK(igraph_i_kleitman_wang(outdeg, indeg, &edges, false)); |
855 | 1.28k | break; |
856 | 1.83k | case IGRAPH_REALIZE_DEGSEQ_INDEX: |
857 | 1.83k | IGRAPH_CHECK(igraph_i_kleitman_wang_index(outdeg, indeg, &edges)); |
858 | 1.28k | break; |
859 | 1.28k | default: |
860 | 0 | IGRAPH_ERROR("Invalid directed degree sequence realization method.", IGRAPH_EINVAL); |
861 | 5.50k | } |
862 | 5.50k | IGRAPH_HANDLE_EXCEPTIONS_END; |
863 | | |
864 | 3.84k | IGRAPH_CHECK(igraph_create(graph, &edges, node_count, true)); |
865 | | |
866 | 3.84k | igraph_vector_int_destroy(&edges); |
867 | 3.84k | IGRAPH_FINALLY_CLEAN(1); |
868 | | |
869 | 3.84k | return IGRAPH_SUCCESS; |
870 | 3.84k | } |
871 | | |
872 | | |
873 | | /** |
874 | | * \ingroup generators |
875 | | * \function igraph_realize_degree_sequence |
876 | | * \brief Generates a graph with the given degree sequence. |
877 | | * |
878 | | * This function generates an undirected graph that realizes a given degree |
879 | | * sequence, or a directed graph that realizes a given pair of out- and |
880 | | * in-degree sequences. |
881 | | * |
882 | | * </para><para> |
883 | | * Simple undirected graphs are constructed using the Havel-Hakimi algorithm |
884 | | * (undirected case), or the analogous Kleitman-Wang algorithm (directed case). |
885 | | * These algorithms work by choosing an arbitrary vertex and connecting all its |
886 | | * stubs to other vertices of highest degree. In the directed case, the |
887 | | * "highest" (in, out) degree pairs are determined based on lexicographic |
888 | | * ordering. This step is repeated until all degrees have been connected up. |
889 | | * |
890 | | * </para><para> |
891 | | * Loopless multigraphs are generated using an analogous algorithm: an arbitrary |
892 | | * vertex is chosen, and it is connected with a single connection to a highest |
893 | | * remaining degee vertex. If self-loops are also allowed, the same algorithm |
894 | | * is used, but if a non-zero vertex remains at the end of the procedure, the |
895 | | * graph is completed by adding self-loops to it. Thus, the result will contain |
896 | | * at most one vertex with self-loops. |
897 | | * |
898 | | * </para><para> |
899 | | * The \c method parameter controls the order in which the vertices to be |
900 | | * connected are chosen. In the undirected case, \c IGRAPH_REALIZE_DEGSEQ_SMALLEST |
901 | | * produces a connected graph when one exists. This makes this method suitable |
902 | | * for constructing trees with a given degree sequence. |
903 | | * |
904 | | * </para><para> |
905 | | * For a undirected simple graph, the time complexity is O(V + alpha(V) * E). |
906 | | * For an undirected multi graph, the time complexity is O(V * E + V log V). |
907 | | * For a directed graph, the time complexity is O(E + V^2 log V). |
908 | | * |
909 | | * </para><para> |
910 | | * References: |
911 | | * |
912 | | * </para><para> |
913 | | * V. Havel: |
914 | | * Poznámka o existenci konečných grafů (A remark on the existence of finite graphs), |
915 | | * Časopis pro pěstování matematiky 80, 477-480 (1955). |
916 | | * http://eudml.org/doc/19050 |
917 | | * |
918 | | * </para><para> |
919 | | * S. L. Hakimi: |
920 | | * On Realizability of a Set of Integers as Degrees of the Vertices of a Linear Graph, |
921 | | * Journal of the SIAM 10, 3 (1962). |
922 | | * https://www.jstor.org/stable/2098770 |
923 | | * |
924 | | * </para><para> |
925 | | * D. J. Kleitman and D. L. Wang: |
926 | | * Algorithms for Constructing Graphs and Digraphs with Given Valences and Factors, |
927 | | * Discrete Mathematics 6, 1 (1973). |
928 | | * https://doi.org/10.1016/0012-365X%2873%2990037-X |
929 | | * |
930 | | * P. L. Erdős, I. Miklós, Z. Toroczkai: |
931 | | * A simple Havel-Hakimi type algorithm to realize graphical degree sequences of directed graphs, |
932 | | * The Electronic Journal of Combinatorics 17.1 (2010). |
933 | | * http://eudml.org/doc/227072 |
934 | | * |
935 | | * </para><para> |
936 | | * Sz. Horvát and C. D. Modes: |
937 | | * Connectedness matters: construction and exact random sampling of connected networks (2021). |
938 | | * https://doi.org/10.1088/2632-072X/abced5 |
939 | | * |
940 | | * \param graph Pointer to an uninitialized graph object. |
941 | | * \param outdeg The degree sequence of an undirected graph (if \p indeg is NULL), |
942 | | * or the out-degree sequence of a directed graph (if \p indeg is given). |
943 | | * \param indeg The in-degree sequence of a directed graph. Pass \c NULL to |
944 | | * generate an undirected graph. |
945 | | * \param allowed_edge_types The types of edges to allow in the graph. See \ref |
946 | | * igraph_edge_type_sw_t. For directed graphs, only \c IGRAPH_SIMPLE_SW is |
947 | | * implemented at this moment. |
948 | | * For undirected graphs, the following values are valid: |
949 | | * \clist |
950 | | * \cli IGRAPH_SIMPLE_SW |
951 | | * simple graphs (i.e. no self-loops or multi-edges allowed). |
952 | | * \cli IGRAPH_LOOPS_SW |
953 | | * single self-loops are allowed, but not multi-edges; currently not implemented. |
954 | | * \cli IGRAPH_MULTI_SW |
955 | | * multi-edges are allowed, but not self-loops. |
956 | | * \cli IGRAPH_LOOPS_SW | IGRAPH_MULTI_SW |
957 | | * both self-loops and multi-edges are allowed. |
958 | | * \endclist |
959 | | * \param method The method to generate the graph. Possible values: |
960 | | * \clist |
961 | | * \cli IGRAPH_REALIZE_DEGSEQ_SMALLEST |
962 | | * The vertex with smallest remaining degree is selected first. The |
963 | | * result is usually a graph with high negative degree assortativity. |
964 | | * In the undirected case, this method is guaranteed to generate a |
965 | | * connected graph, regardless of whether multi-edges are allowed, |
966 | | * provided that a connected realization exists (see Horvát and Modes, |
967 | | * 2021, as well as http://szhorvat.net/pelican/hh-connected-graphs.html). |
968 | | * This method can be used to construct a tree from its degrees. |
969 | | * In the directed case it tends to generate weakly connected graphs, |
970 | | * but this is not guaranteed. |
971 | | * \cli IGRAPH_REALIZE_DEGSEQ_LARGEST |
972 | | * The vertex with the largest remaining degree is selected first. The |
973 | | * result is usually a graph with high positive degree assortativity, and |
974 | | * is often disconnected. |
975 | | * \cli IGRAPH_REALIZE_DEGSEQ_INDEX |
976 | | * The vertices are selected in order of their index (i.e. their position |
977 | | * in the degree vector). Note that sorting the degree vector and using |
978 | | * the \c INDEX method is not equivalent to the \c SMALLEST method above, |
979 | | * as \c SMALLEST uses the smallest \em remaining degree for selecting |
980 | | * vertices, not the smallest \em initial degree. |
981 | | * \endclist |
982 | | * \return Error code: |
983 | | * \clist |
984 | | * \cli IGRAPH_UNIMPLEMENTED |
985 | | * The requested method is not implemented. |
986 | | * \cli IGRAPH_ENOMEM |
987 | | * There is not enough memory to perform the operation. |
988 | | * \cli IGRAPH_EINVAL |
989 | | * Invalid method parameter, or invalid in- and/or out-degree vectors. |
990 | | * The degree vectors should be non-negative, the length |
991 | | * and sum of \p outdeg and \p indeg should match for directed graphs. |
992 | | * \endclist |
993 | | * |
994 | | * \sa \ref igraph_is_graphical() to test graphicality without generating a graph; |
995 | | * \ref igraph_realize_bipartite_degree_sequence() to create bipartite graphs |
996 | | * from two degree sequence; |
997 | | * \ref igraph_degree_sequence_game() to generate random graphs with a given |
998 | | * degree sequence; |
999 | | * \ref igraph_k_regular_game() to generate random regular graphs; |
1000 | | * \ref igraph_rewire() to randomly rewire the edges of a graph while |
1001 | | * preserving its degree sequence. |
1002 | | * |
1003 | | * \example examples/simple/igraph_realize_degree_sequence.c |
1004 | | */ |
1005 | | |
1006 | | igraph_error_t igraph_realize_degree_sequence( |
1007 | | igraph_t *graph, |
1008 | | const igraph_vector_int_t *outdeg, const igraph_vector_int_t *indeg, |
1009 | | igraph_edge_type_sw_t allowed_edge_types, |
1010 | | igraph_realize_degseq_t method) |
1011 | 32.6k | { |
1012 | 32.6k | bool directed = indeg != NULL; |
1013 | | |
1014 | 32.6k | if (directed) { |
1015 | 5.50k | return igraph_i_realize_directed_degree_sequence(graph, outdeg, indeg, allowed_edge_types, method); |
1016 | 27.1k | } else { |
1017 | 27.1k | return igraph_i_realize_undirected_degree_sequence(graph, outdeg, allowed_edge_types, method); |
1018 | 27.1k | } |
1019 | 32.6k | } |
1020 | | |
1021 | | |
1022 | | // Uses index order to construct an undirected bipartite graph. |
1023 | | // degree1 is considered to range from index [0, len(degree1)[, |
1024 | | // so for this implementation degree1 is always the source degree |
1025 | | // sequence and degree2 is always the dest degree sequence. |
1026 | | static igraph_error_t igraph_i_realize_undirected_bipartite_index( |
1027 | | igraph_t *graph, |
1028 | | const igraph_vector_int_t *degree1, const igraph_vector_int_t *degree2, |
1029 | | igraph_bool_t multiedges |
1030 | 3.67k | ) { |
1031 | 3.67k | igraph_int_t ec = 0; // The number of edges added so far |
1032 | 3.67k | igraph_int_t n1 = igraph_vector_int_size(degree1); |
1033 | 3.67k | igraph_int_t n2 = igraph_vector_int_size(degree2); |
1034 | 3.67k | igraph_vector_int_t edges; |
1035 | 3.67k | igraph_int_t ds1_sum; |
1036 | 3.67k | igraph_int_t ds2_sum; |
1037 | | |
1038 | 3.67k | std::vector<vd_pair> vertices1; |
1039 | 3.67k | std::vector<vd_pair> vertices2; |
1040 | 3.67k | std::vector<vd_pair> *src_vs = &vertices1; |
1041 | 3.67k | std::vector<vd_pair> *dest_vs = &vertices2; |
1042 | | |
1043 | 3.67k | IGRAPH_CHECK(igraph_i_safe_vector_int_sum(degree1, &ds1_sum)); |
1044 | 3.67k | IGRAPH_CHECK(igraph_i_safe_vector_int_sum(degree2, &ds2_sum)); |
1045 | | |
1046 | 3.67k | if (ds1_sum != ds2_sum) { |
1047 | 0 | goto fail; |
1048 | 0 | } |
1049 | | |
1050 | | // If both degree sequences are empty, it's bigraphical |
1051 | 3.67k | if (!(n1 == 0 && n2 == 0)) { |
1052 | 3.66k | if (igraph_vector_int_min(degree1) < 0 || igraph_vector_int_min(degree2) < 0) { |
1053 | 0 | goto fail; |
1054 | 0 | } |
1055 | 3.66k | } |
1056 | | |
1057 | 3.67k | vertices1.reserve(n1); |
1058 | 3.67k | vertices2.reserve(n2); |
1059 | | |
1060 | 638k | for (igraph_int_t i = 0; i < n1; i++) { |
1061 | 634k | vertices1.push_back(vd_pair(i, VECTOR(*degree1)[i])); |
1062 | 634k | } |
1063 | 638k | for (igraph_int_t i = 0; i < n2; i++) { |
1064 | 634k | vertices2.push_back(vd_pair(i + n1, VECTOR(*degree2)[i])); |
1065 | 634k | } |
1066 | | |
1067 | 3.67k | IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, ds1_sum + ds2_sum); |
1068 | | |
1069 | 637k | while (!vertices1.empty() && !vertices2.empty()) { |
1070 | | // Go by index, so we start in ds1, so ds2 needs to be sorted. |
1071 | 634k | std::stable_sort(vertices2.begin(), vertices2.end(), degree_greater<vd_pair>); |
1072 | | // No sorting of ds1 needed for index case |
1073 | 634k | vd_pair vd_src = vertices1.front(); |
1074 | | // No multiedges - Take the first vertex, connect to the largest delta in opposite partition |
1075 | 634k | if (!multiedges) { |
1076 | | // Remove the source degrees |
1077 | 283k | src_vs->erase(src_vs->begin()); |
1078 | | |
1079 | 283k | if (vd_src.degree == 0) { |
1080 | 248k | continue; |
1081 | 248k | } |
1082 | | |
1083 | 35.1k | if (dest_vs->size() < size_t(vd_src.degree)) { |
1084 | 154 | goto fail; |
1085 | 154 | } |
1086 | | |
1087 | 84.5k | for (igraph_int_t i = 0; i < vd_src.degree; i++) { |
1088 | 49.8k | if ((*dest_vs)[i].degree == 0) { |
1089 | | // Not enough non-zero remaining degree vertices in opposite partition. |
1090 | | // Not graphical. |
1091 | 248 | goto fail; |
1092 | 248 | } |
1093 | | |
1094 | 49.6k | (*dest_vs)[i].degree--; |
1095 | | |
1096 | 49.6k | VECTOR(edges)[2*(ec + i)] = vd_src.vertex; |
1097 | 49.6k | VECTOR(edges)[2*(ec + i) + 1] = (*dest_vs)[i].vertex; |
1098 | 49.6k | } |
1099 | 34.7k | ec += vd_src.degree; |
1100 | 34.7k | } |
1101 | | // If multiedges are allowed |
1102 | 350k | else { |
1103 | | // If this is the last edge to be created from this vertex, we remove it. |
1104 | 350k | if (src_vs->front().degree <= 1) { |
1105 | 317k | src_vs->erase(src_vs->begin()); |
1106 | 317k | } else { |
1107 | 33.5k | src_vs->front().degree--; |
1108 | 33.5k | } |
1109 | | |
1110 | 350k | if (vd_src.degree == 0) { |
1111 | 280k | continue; |
1112 | 280k | } |
1113 | | |
1114 | 69.7k | if (dest_vs->size() < size_t(1)) { |
1115 | 0 | goto fail; |
1116 | 0 | } |
1117 | | // We should never decrement below zero, but check just in case. |
1118 | 69.7k | IGRAPH_ASSERT((*dest_vs)[0].degree - 1 >= 0); |
1119 | | |
1120 | | // Connect to the opposite partition |
1121 | 69.7k | (*dest_vs)[0].degree--; |
1122 | | |
1123 | 69.7k | VECTOR(edges)[2 * ec] = vd_src.vertex; |
1124 | 69.7k | VECTOR(edges)[2 * ec + 1] = (*dest_vs)[0].vertex; |
1125 | 69.7k | ec++; |
1126 | 69.7k | } |
1127 | 634k | } |
1128 | 3.26k | IGRAPH_CHECK(igraph_create(graph, &edges, n1 + n2, false)); |
1129 | | |
1130 | 3.26k | igraph_vector_int_destroy(&edges); |
1131 | 3.26k | IGRAPH_FINALLY_CLEAN(1); |
1132 | | |
1133 | 3.26k | return IGRAPH_SUCCESS; |
1134 | | |
1135 | 402 | fail: |
1136 | 402 | IGRAPH_ERRORF("The given bidegree sequence cannot be realized as a bipartite %sgraph.", |
1137 | 402 | IGRAPH_EINVAL, multiedges ? "multi" : "simple "); |
1138 | 402 | } |
1139 | | |
1140 | | /** |
1141 | | * \function igraph_realize_bipartite_degree_sequence |
1142 | | * \brief Generates a bipartite graph with the given bidegree sequence. |
1143 | | * |
1144 | | * This function generates a bipartite graph with the given bidegree sequence, |
1145 | | * using a Havel-Hakimi-like construction algorithm. The order in which vertices |
1146 | | * are connected up is controlled by the \p method parameter. When using the |
1147 | | * \c IGRAPH_REALIZE_DEGSEQ_SMALLEST method, it is ensured that the graph will be |
1148 | | * connected if and only if the given bidegree sequence is potentially connected. |
1149 | | * |
1150 | | * </para><para> |
1151 | | * The vertices of the graph will be ordered so that those having \p degrees1 |
1152 | | * come first, followed by \p degrees2. |
1153 | | * |
1154 | | * \param graph Pointer to an uninitialized graph object. |
1155 | | * \param degrees1 The degree sequence of the first partition. |
1156 | | * \param degrees2 The degree sequence of the second partition. |
1157 | | * \param allowed_edge_types The types of edges to allow in the graph. |
1158 | | * \clist |
1159 | | * \cli IGRAPH_SIMPLE_SW |
1160 | | * simple graph (i.e. no multi-edges allowed). |
1161 | | * \cli IGRAPH_MULTI_SW |
1162 | | * multi-edges are allowed |
1163 | | * \endclist |
1164 | | * \param method Controls the order in which vertices are selected for connection. |
1165 | | * Possible values: |
1166 | | * \clist |
1167 | | * \cli IGRAPH_REALIZE_DEGSEQ_SMALLEST |
1168 | | * The vertex with smallest remaining degree is selected first, from either |
1169 | | * partition. The result is usually a graph with high negative degree |
1170 | | * assortativity. This method is guaranteed to generate a connected graph, |
1171 | | * if one exists. |
1172 | | * \cli IGRAPH_REALIZE_DEGSEQ_LARGEST |
1173 | | * The vertex with the largest remaining degree is selected first, from |
1174 | | * either parition. The result is usually a graph with high positive degree |
1175 | | * assortativity, and is often disconnected. |
1176 | | * \cli IGRAPH_REALIZE_DEGSEQ_INDEX |
1177 | | * The vertices are selected in order of their index. |
1178 | | * \endclist |
1179 | | * \return Error code. |
1180 | | * \sa \ref igraph_is_bigraphical() to test bigraphicality without generating a graph. |
1181 | | */ |
1182 | | |
1183 | | igraph_error_t igraph_realize_bipartite_degree_sequence( |
1184 | | igraph_t *graph, |
1185 | | const igraph_vector_int_t *degrees1, const igraph_vector_int_t *degrees2, |
1186 | | const igraph_edge_type_sw_t allowed_edge_types, const igraph_realize_degseq_t method |
1187 | 11.0k | ) { |
1188 | 11.0k | IGRAPH_HANDLE_EXCEPTIONS_BEGIN; |
1189 | | |
1190 | 11.0k | igraph_int_t ec = 0; // The number of edges added so far |
1191 | 11.0k | igraph_int_t n1 = igraph_vector_int_size(degrees1); |
1192 | 11.0k | igraph_int_t n2 = igraph_vector_int_size(degrees2); |
1193 | 11.0k | igraph_vector_int_t edges; |
1194 | 11.0k | igraph_int_t ds1_sum; |
1195 | 11.0k | igraph_int_t ds2_sum; |
1196 | 11.0k | igraph_bool_t multiedges; |
1197 | 11.0k | igraph_bool_t largest; |
1198 | 11.0k | std::vector<vd_pair> vertices1; |
1199 | 11.0k | std::vector<vd_pair> vertices2; |
1200 | | |
1201 | | // Bipartite graphs can't have self loops, so we ignore those. |
1202 | 11.0k | if (allowed_edge_types & IGRAPH_I_MULTI_EDGES_SW) { |
1203 | | // Multiedges allowed |
1204 | 5.50k | multiedges = true; |
1205 | 5.50k | } else { |
1206 | | // No multiedges |
1207 | 5.50k | multiedges = false; |
1208 | 5.50k | } |
1209 | | |
1210 | 11.0k | switch (method) { |
1211 | 3.67k | case IGRAPH_REALIZE_DEGSEQ_SMALLEST: |
1212 | 3.67k | largest = false; |
1213 | 3.67k | break; |
1214 | 3.67k | case IGRAPH_REALIZE_DEGSEQ_LARGEST: |
1215 | 3.67k | largest = true; |
1216 | 3.67k | break; |
1217 | 3.67k | case IGRAPH_REALIZE_DEGSEQ_INDEX: |
1218 | 3.67k | return igraph_i_realize_undirected_bipartite_index(graph, degrees1, degrees2, multiedges); |
1219 | 0 | default: |
1220 | 0 | IGRAPH_ERROR("Invalid bipartite degree sequence realization method.", IGRAPH_EINVAL); |
1221 | 11.0k | } |
1222 | | |
1223 | 7.34k | IGRAPH_CHECK(igraph_i_safe_vector_int_sum(degrees1, &ds1_sum)); |
1224 | 7.34k | IGRAPH_CHECK(igraph_i_safe_vector_int_sum(degrees2, &ds2_sum)); |
1225 | | |
1226 | | // Degree sequences of the two partitions must sum to the same value |
1227 | 7.34k | if (ds1_sum != ds2_sum) { |
1228 | 0 | goto fail; |
1229 | 0 | } |
1230 | | |
1231 | | // If both degree sequences are empty, it's bigraphical |
1232 | 7.34k | if (!(n1 == 0 && n2 == 0)) { |
1233 | 7.33k | if (igraph_vector_int_min(degrees1) < 0 || igraph_vector_int_min(degrees2) < 0) { |
1234 | 0 | goto fail; |
1235 | 0 | } |
1236 | 7.33k | } |
1237 | | |
1238 | 7.34k | vertices1.reserve(n1); |
1239 | 7.34k | vertices2.reserve(n2); |
1240 | | |
1241 | 1.27M | for (igraph_int_t i = 0; i < n1; i++) { |
1242 | 1.26M | vertices1.push_back(vd_pair(i, VECTOR(*degrees1)[i])); |
1243 | 1.26M | } |
1244 | 1.27M | for (igraph_int_t i = 0; i < n2; i++) { |
1245 | 1.26M | vertices2.push_back(vd_pair(i + n1, VECTOR(*degrees2)[i])); |
1246 | 1.26M | } |
1247 | | |
1248 | 7.34k | IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, ds1_sum + ds2_sum); |
1249 | | |
1250 | | |
1251 | 7.34k | std::vector<vd_pair> *src_vs; |
1252 | 7.34k | std::vector<vd_pair> *dest_vs; |
1253 | | |
1254 | 1.89M | while (!vertices1.empty() && !vertices2.empty()) { |
1255 | | // Sort in non-increasing order. |
1256 | | // Note: for the smallest method, we can skip sorting the smaller ds, minor optimization. |
1257 | | // (i.e., we only need to sort the dest partition, since we always just remove the back of the min partition) |
1258 | 1.89M | std::stable_sort(vertices1.begin(), vertices1.end(), degree_greater<vd_pair>); |
1259 | 1.89M | std::stable_sort(vertices2.begin(), vertices2.end(), degree_greater<vd_pair>); |
1260 | | |
1261 | 1.89M | vd_pair vd_src(-1, -1); |
1262 | | |
1263 | 1.89M | if (!largest) { |
1264 | 1.26M | vd_pair min1 = vertices1.back(); |
1265 | 1.26M | vd_pair min2 = vertices2.back(); |
1266 | 1.26M | if (min1.degree <= min2.degree) { |
1267 | 651k | src_vs = &vertices1; |
1268 | 651k | dest_vs = &vertices2; |
1269 | 651k | } else { |
1270 | 608k | src_vs = &vertices2; |
1271 | 608k | dest_vs = &vertices1; |
1272 | 608k | } |
1273 | | |
1274 | 1.26M | vd_src = src_vs->back(); |
1275 | | |
1276 | 1.26M | } else { |
1277 | 629k | vd_pair max1 = vertices1.front(); |
1278 | 629k | vd_pair max2 = vertices2.front(); |
1279 | | |
1280 | 629k | if (max1.degree >= max2.degree) { |
1281 | 615k | src_vs = &vertices1; |
1282 | 615k | dest_vs = &vertices2; |
1283 | 615k | } else { |
1284 | 13.7k | src_vs = &vertices2; |
1285 | 13.7k | dest_vs = &vertices1; |
1286 | 13.7k | } |
1287 | | |
1288 | 629k | vd_src = src_vs->front(); |
1289 | 629k | } |
1290 | | |
1291 | 1.89M | IGRAPH_ASSERT(vd_src.degree >= 0); |
1292 | | |
1293 | 1.89M | if (!multiedges) { |
1294 | | // Remove the smallest element |
1295 | 892k | if (!largest) { |
1296 | 619k | src_vs->pop_back(); |
1297 | 619k | } else { |
1298 | | // Remove the largest element. |
1299 | 273k | src_vs->erase(src_vs->begin()); |
1300 | 273k | } |
1301 | | |
1302 | 892k | if (vd_src.degree == 0) { |
1303 | 816k | continue; |
1304 | 816k | } |
1305 | 76.3k | if (dest_vs->size() < size_t(vd_src.degree)) { |
1306 | 566 | goto fail; |
1307 | 566 | } |
1308 | 175k | for (igraph_int_t i = 0; i < vd_src.degree; i++) { |
1309 | | // Decrement the degree of the delta largest vertices in the opposite partition |
1310 | | |
1311 | 100k | if ((*dest_vs)[i].degree == 0) { |
1312 | | // Not enough non-zero remaining degree vertices in opposite partition. |
1313 | | // Not graphical. |
1314 | 238 | goto fail; |
1315 | 238 | } |
1316 | | |
1317 | 100k | (*dest_vs)[i].degree--; |
1318 | | |
1319 | 100k | VECTOR(edges)[2 * (ec + i)] = vd_src.vertex; |
1320 | 100k | VECTOR(edges)[2 * (ec + i) + 1] = (*dest_vs)[i].vertex; |
1321 | 100k | } |
1322 | 75.5k | ec += vd_src.degree; |
1323 | 75.5k | } |
1324 | | // If multiedges are allowed |
1325 | 997k | else { |
1326 | | // The smallest degree is in the back, and we know it is in vertices1 |
1327 | | // If this is the last edge to be created from this vertex, we remove it. |
1328 | 997k | if (!largest) { |
1329 | 640k | if (src_vs->back().degree <= 1) { |
1330 | 620k | src_vs->pop_back(); |
1331 | 620k | } else { |
1332 | | // Otherwise we decrement its degrees by 1 for the edge we are about to create. |
1333 | 20.2k | src_vs->back().degree--; |
1334 | 20.2k | } |
1335 | 640k | } else { |
1336 | 356k | if (src_vs->front().degree <= 1) { |
1337 | 317k | src_vs->erase(src_vs->begin()); |
1338 | 317k | } else { |
1339 | 39.4k | src_vs->front().degree--; |
1340 | 39.4k | } |
1341 | 356k | } |
1342 | | |
1343 | 997k | if (vd_src.degree == 0) { |
1344 | 857k | continue; |
1345 | 857k | } |
1346 | | |
1347 | 139k | if (dest_vs->size() < size_t(1)) { |
1348 | 0 | goto fail; |
1349 | 0 | } |
1350 | | // We should never decrement below zero, but check just in case. |
1351 | 139k | IGRAPH_ASSERT((*dest_vs)[0].degree - 1 >= 0); |
1352 | | |
1353 | | // Connect to the opposite partition |
1354 | 139k | (*dest_vs)[0].degree--; |
1355 | | |
1356 | 139k | VECTOR(edges)[2 * ec] = vd_src.vertex; |
1357 | 139k | VECTOR(edges)[2 * ec + 1] = (*dest_vs)[0].vertex; |
1358 | 139k | ec++; |
1359 | 139k | } |
1360 | 1.89M | } |
1361 | 6.53k | IGRAPH_CHECK(igraph_create(graph, &edges, n1 + n2, IGRAPH_UNDIRECTED)); |
1362 | | |
1363 | 6.53k | igraph_vector_int_destroy(&edges); |
1364 | 6.53k | IGRAPH_FINALLY_CLEAN(1); |
1365 | | |
1366 | 6.53k | return IGRAPH_SUCCESS; |
1367 | | |
1368 | 804 | fail: |
1369 | 804 | IGRAPH_ERRORF("The given bidegree sequence cannot be realized as a bipartite %sgraph.", |
1370 | 804 | IGRAPH_EINVAL, multiedges ? "multi" : "simple "); |
1371 | | |
1372 | 804 | IGRAPH_HANDLE_EXCEPTIONS_END; |
1373 | 0 | } |