/src/igraph/src/centrality/truss.cpp
Line | Count | Source |
1 | | /* |
2 | | Copyright 2017 The Johns Hopkins University Applied Physics Laboratory LLC. All Rights Reserved. |
3 | | Copyright 2021 The igraph team. |
4 | | |
5 | | Truss algorithm for cohesive subgroups. |
6 | | |
7 | | Author: Alex Perrone |
8 | | Date: 2017-08-03 |
9 | | Minor edits: The igraph team, 2021 |
10 | | |
11 | | This program is free software; you can redistribute it and/or modify |
12 | | it under the terms of the GNU General Public License as published by |
13 | | the Free Software Foundation; either version 2 of the License, or |
14 | | (at your option) any later version. |
15 | | |
16 | | This program is distributed in the hope that it will be useful, |
17 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
18 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
19 | | GNU General Public License for more details. |
20 | | |
21 | | You should have received a copy of the GNU General Public License |
22 | | along with this program; if not, write to the Free Software |
23 | | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
24 | | 02110-1301 USA |
25 | | |
26 | | */ |
27 | | |
28 | | #include "igraph_community.h" |
29 | | |
30 | | #include "igraph_adjlist.h" |
31 | | #include "igraph_error.h" |
32 | | #include "igraph_interface.h" |
33 | | #include "igraph_motifs.h" |
34 | | #include "igraph_structural.h" |
35 | | |
36 | | #include "core/exceptions.h" |
37 | | #include "core/interruption.h" |
38 | | |
39 | | #include <vector> |
40 | | #include <unordered_set> |
41 | | |
42 | | using std::vector; |
43 | | using std::unordered_set; |
44 | | |
45 | | |
46 | | // Unpack the triangles as a vector of vertices to be a vector of edges. |
47 | | // So, instead of the triangle specified as vertices [1, 2, 3], return the |
48 | | // edges as [1, 2, 1, 3, 2, 3] so that the support can be computed. |
49 | 1.31k | static igraph_error_t igraph_truss_i_unpack(const igraph_vector_int_t *tri, igraph_vector_int_t *unpacked_tri) { |
50 | 1.31k | igraph_int_t num_triangles = igraph_vector_int_size(tri); |
51 | | |
52 | 1.31k | IGRAPH_CHECK(igraph_vector_int_resize(unpacked_tri, 2 * num_triangles)); |
53 | | |
54 | 23.7k | for (igraph_int_t i = 0, j = 0; i < num_triangles; i += 3, j += 6) { |
55 | 22.4k | VECTOR(*unpacked_tri)[j] = VECTOR(*unpacked_tri)[j+2] = VECTOR(*tri)[i]; |
56 | 22.4k | VECTOR(*unpacked_tri)[j+1] = VECTOR(*unpacked_tri)[j+4] = VECTOR(*tri)[i+1]; |
57 | 22.4k | VECTOR(*unpacked_tri)[j+3] = VECTOR(*unpacked_tri)[j+5] = VECTOR(*tri)[i+2]; |
58 | 22.4k | } |
59 | | |
60 | 1.31k | return IGRAPH_SUCCESS; |
61 | 1.31k | } |
62 | | |
63 | | |
64 | | // Compute the edge support, i.e. number of triangles each edge occurs in. |
65 | | // Time complexity: O(m), where m is the number of edges listed in eid. |
66 | 1.31k | static void igraph_truss_i_compute_support(const igraph_vector_int_t *eid, igraph_vector_int_t *support) { |
67 | 1.31k | igraph_int_t m = igraph_vector_int_size(eid); |
68 | 68.5k | for (igraph_int_t i = 0; i < m; ++i) { |
69 | 67.2k | VECTOR(*support)[VECTOR(*eid)[i]] += 1; |
70 | 67.2k | } |
71 | 1.31k | } |
72 | | |
73 | | |
74 | | /* internal function doing the computations once the support is defined */ |
75 | | static igraph_error_t igraph_i_trussness(const igraph_t *graph, igraph_vector_int_t *support, |
76 | 1.31k | igraph_vector_int_t *trussness) { |
77 | 1.31k | IGRAPH_HANDLE_EXCEPTIONS_BEGIN; |
78 | | |
79 | 1.31k | igraph_adjlist_t adjlist; |
80 | 1.31k | igraph_vector_int_t commonNeighbors; |
81 | 1.31k | igraph_vector_bool_t completed; |
82 | | |
83 | | // C++ data structures |
84 | 1.31k | vector< unordered_set<igraph_int_t> > vec; |
85 | | |
86 | | // Allocate memory for result |
87 | 1.31k | igraph_int_t no_of_edges = igraph_vector_int_size(support); |
88 | 1.31k | IGRAPH_CHECK(igraph_vector_int_resize(trussness, no_of_edges)); |
89 | 1.31k | if (no_of_edges == 0) { |
90 | 45 | return IGRAPH_SUCCESS; |
91 | 45 | } |
92 | | |
93 | | // Get max possible value = max entry in support. |
94 | | // This cannot be computed if there are no edges, hence the above check |
95 | 1.27k | igraph_int_t max = igraph_vector_int_max(support); |
96 | | |
97 | | // Initialize completed edges. |
98 | 1.27k | IGRAPH_VECTOR_BOOL_INIT_FINALLY(&completed, no_of_edges); |
99 | | |
100 | | // The vector of levels. Each level of the vector is a set of edges initially |
101 | | // at that level of support, where support is # of triangles the edge is in. |
102 | 1.27k | vec.resize(max + 1); |
103 | | |
104 | | // Add each edge to its appropriate level of support. |
105 | 31.3k | for (igraph_int_t i = 0; i < no_of_edges; ++i) { |
106 | 30.0k | vec[VECTOR(*support)[i]].insert(i); // insert edge i into its support level |
107 | 30.0k | } |
108 | | |
109 | | // Record the trussness of edges at level 0. These edges are not part |
110 | | // of any triangles, so there's not much to do and we "complete" them |
111 | 10.9k | for (auto edge : vec[0]) { |
112 | 10.9k | VECTOR(*trussness)[edge] = 2; |
113 | 10.9k | VECTOR(completed)[edge] = true; |
114 | 10.9k | } |
115 | | |
116 | | // Initialize variables needed below. |
117 | 1.27k | IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, IGRAPH_ALL, IGRAPH_NO_LOOPS, IGRAPH_MULTIPLE)); |
118 | 1.27k | IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); |
119 | 1.27k | IGRAPH_VECTOR_INT_INIT_FINALLY(&commonNeighbors, 0); |
120 | | |
121 | | // Move through the levels, one level at a time, starting at first level. |
122 | 5.19k | for (igraph_int_t level = 1; level <= max; ++level) { |
123 | | |
124 | | /* Track down edges one at a time */ |
125 | 23.1k | while (!vec[level].empty()) { |
126 | 19.1k | IGRAPH_ALLOW_INTERRUPTION(); |
127 | | |
128 | 19.1k | igraph_int_t seed = *vec[level].begin(); // pull out the first edge |
129 | 19.1k | vec[level].erase(seed); // remove the first element |
130 | | |
131 | | /* Find the vertices of this edge */ |
132 | 19.1k | igraph_int_t fromVertex = IGRAPH_FROM(graph, seed); |
133 | 19.1k | igraph_int_t toVertex = IGRAPH_TO(graph, seed); |
134 | | |
135 | | /* Find neighbors of both vertices. If they run into each other, |
136 | | * there is a triangle. We rely on the neighbor lists being sorted, |
137 | | * as guaranteed by igraph_adjlist_init(), when computing intersections. */ |
138 | 19.1k | igraph_vector_int_t *fromNeighbors = igraph_adjlist_get(&adjlist, fromVertex); |
139 | 19.1k | igraph_vector_int_t *toNeighbors = igraph_adjlist_get(&adjlist, toVertex); |
140 | 19.1k | igraph_vector_int_t *q1 = fromNeighbors; |
141 | 19.1k | igraph_vector_int_t *q2 = toNeighbors; |
142 | | |
143 | 19.1k | if (igraph_vector_int_size(q1) > igraph_vector_int_size(q2)) { |
144 | | // case: #fromNeighbors > #toNeigbors, so make q1 the smaller set. |
145 | 5.57k | q1 = toNeighbors; |
146 | 5.57k | q2 = fromNeighbors; |
147 | 5.57k | } |
148 | | |
149 | | // Intersect the neighbors. |
150 | 19.1k | IGRAPH_CHECK(igraph_vector_int_intersect_sorted(q1, q2, &commonNeighbors)); |
151 | | |
152 | | /* Go over the overlapping neighbors and check each */ |
153 | 19.1k | igraph_int_t ncommon = igraph_vector_int_size(&commonNeighbors); |
154 | 86.4k | for (igraph_int_t j = 0; j < ncommon; j++) { |
155 | 67.2k | igraph_int_t n = VECTOR(commonNeighbors)[j]; // the common neighbor |
156 | 67.2k | igraph_int_t e1, e2; |
157 | | |
158 | 67.2k | igraph_get_eid(graph, &e1, fromVertex, n, IGRAPH_UNDIRECTED, /* error= */ false); |
159 | 67.2k | igraph_get_eid(graph, &e2, toVertex, n, IGRAPH_UNDIRECTED, /* error= */ false); |
160 | 67.2k | IGRAPH_ASSERT(e1 >= 0 && e2 >= 0); |
161 | | |
162 | 67.2k | bool e1_complete = VECTOR(completed)[e1]; |
163 | 67.2k | bool e2_complete = VECTOR(completed)[e2]; |
164 | | |
165 | 67.2k | if (!e1_complete && !e2_complete) { |
166 | 22.4k | igraph_int_t newLevel; |
167 | | |
168 | | // Demote this edge, if higher than current level. |
169 | 22.4k | if (VECTOR(*support)[e1] > level) { |
170 | 9.11k | VECTOR(*support)[e1] -= 1; // decrement the level |
171 | 9.11k | newLevel = VECTOR(*support)[e1]; |
172 | 9.11k | vec[newLevel].insert(e1); |
173 | 9.11k | vec[newLevel + 1].erase(e1); // the old level |
174 | 9.11k | } |
175 | | // Demote this edge, if higher than current level. |
176 | 22.4k | if (VECTOR(*support)[e2] > level) { |
177 | 10.9k | VECTOR(*support)[e2] -= 1; // decrement the level |
178 | 10.9k | newLevel = VECTOR(*support)[e2]; |
179 | 10.9k | vec[newLevel].insert(e2); |
180 | 10.9k | vec[newLevel + 1].erase(e2); // the old level |
181 | 10.9k | } |
182 | 22.4k | } |
183 | 67.2k | } |
184 | | // Record this edge; its level is its trussness. |
185 | 19.1k | VECTOR(*trussness)[seed] = level + 2; |
186 | 19.1k | VECTOR(completed)[seed] = true; // mark as complete |
187 | 19.1k | igraph_vector_int_clear(&commonNeighbors); |
188 | 19.1k | } // end while |
189 | 3.92k | } // end for-loop over levels |
190 | | |
191 | | // Clean up. |
192 | 1.27k | igraph_vector_int_destroy(&commonNeighbors); |
193 | 1.27k | igraph_adjlist_destroy(&adjlist); |
194 | 1.27k | igraph_vector_bool_destroy(&completed); |
195 | 1.27k | IGRAPH_FINALLY_CLEAN(3); |
196 | | |
197 | 1.27k | return IGRAPH_SUCCESS; |
198 | | |
199 | 1.27k | IGRAPH_HANDLE_EXCEPTIONS_END; |
200 | 0 | } |
201 | | |
202 | | |
203 | | /** |
204 | | * \function igraph_trussness |
205 | | * \brief Finding the "trussness" of the edges in a network. |
206 | | * |
207 | | * A k-truss is a subgraph in which every edge occurs in at least <code>k-2</code> triangles |
208 | | * in the subgraph. The trussness of an edge indicates the highest k-truss that |
209 | | * the edge occurs in. |
210 | | * |
211 | | * </para><para> |
212 | | * This function returns the highest \c k for each edge. If you are interested in |
213 | | * a particular k-truss subgraph, you can subset the graph to those edges |
214 | | * which are <code>>= k</code> because each k-truss is a subgraph of a <code>(k–1)</code>-truss |
215 | | * Thus, to get all 4-trusses, take <code>k >= 4</code> because the 5-trusses, 6-trusses, |
216 | | * etc. need to be included. |
217 | | * |
218 | | * </para><para> |
219 | | * The current implementation of this function iteratively decrements support |
220 | | * of each edge using O(|E|) space and O(|E|^1.5) time. The implementation does |
221 | | * not support multigraphs; use \ref igraph_simplify() to collapse edges before |
222 | | * calling this function. |
223 | | * |
224 | | * </para><para> |
225 | | * Reference: |
226 | | * |
227 | | * </para><para> |
228 | | * See Algorithm 2 in: |
229 | | * Wang, Jia, and James Cheng. "Truss decomposition in massive networks." |
230 | | * Proceedings of the VLDB Endowment 5.9 (2012): 812-823. |
231 | | * https://doi.org/10.14778/2311906.2311909 |
232 | | * |
233 | | * \param graph The input graph. Loop edges are allowed; multigraphs are not. |
234 | | * \param truss Pointer to initialized vector of truss values that will |
235 | | * indicate the highest k-truss each edge occurs in. It will be resized as |
236 | | * needed. |
237 | | * \return Error code. |
238 | | * |
239 | | * Time complexity: It should be O(|E|^1.5) according to the reference. |
240 | | */ |
241 | 1.31k | igraph_error_t igraph_trussness(const igraph_t* graph, igraph_vector_int_t* trussness) { |
242 | 1.31k | igraph_vector_int_t triangles, support, unpacked_triangles, eid; |
243 | 1.31k | igraph_bool_t is_multigraph; |
244 | | |
245 | | /* Check whether the graph is a multigraph; trussness will not work for these */ |
246 | 1.31k | IGRAPH_CHECK(igraph_has_multiple(graph, &is_multigraph)); |
247 | 1.31k | if (! is_multigraph && igraph_is_directed(graph)) { |
248 | | /* Directed graphs with mutual edges are effectively multigraphs |
249 | | * when edge directions are ignored. */ |
250 | 0 | IGRAPH_CHECK(igraph_has_mutual(graph, &is_multigraph, /* loops */ false)); |
251 | 0 | } |
252 | 1.31k | if (is_multigraph) { |
253 | 0 | IGRAPH_ERROR("Trussness is not implemented for graphs with multi-edges.", IGRAPH_UNIMPLEMENTED); |
254 | 0 | } |
255 | | |
256 | | /* Manage the stack to make it memory safe: do not change the order of |
257 | | * initialization of the following four vectors */ |
258 | 1.31k | IGRAPH_VECTOR_INT_INIT_FINALLY(&support, igraph_ecount(graph)); |
259 | 1.31k | IGRAPH_VECTOR_INT_INIT_FINALLY(&eid, 0); |
260 | 1.31k | IGRAPH_VECTOR_INT_INIT_FINALLY(&unpacked_triangles, 0); |
261 | 1.31k | IGRAPH_VECTOR_INT_INIT_FINALLY(&triangles, 0); |
262 | | |
263 | | // List the triangles as vertex triplets. |
264 | 1.31k | IGRAPH_CHECK(igraph_list_triangles(graph, &triangles)); |
265 | | |
266 | | // Unpack the triangles from vertex list to edge list. |
267 | 1.31k | IGRAPH_CHECK(igraph_truss_i_unpack(&triangles, &unpacked_triangles)); |
268 | 1.31k | igraph_vector_int_destroy(&triangles); |
269 | 1.31k | IGRAPH_FINALLY_CLEAN(1); |
270 | | |
271 | | // Get the edge IDs of the unpacked triangles. Note: a given eid can occur |
272 | | // multiple times in this list if it is in multiple triangles. |
273 | 1.31k | IGRAPH_CHECK(igraph_get_eids(graph, &eid, &unpacked_triangles, /* directed = */ false, /* error = */ true)); |
274 | 1.31k | igraph_vector_int_destroy(&unpacked_triangles); |
275 | 1.31k | IGRAPH_FINALLY_CLEAN(1); |
276 | | |
277 | | // Compute the support of the edges. |
278 | 1.31k | igraph_truss_i_compute_support(&eid, &support); |
279 | 1.31k | igraph_vector_int_destroy(&eid); |
280 | 1.31k | IGRAPH_FINALLY_CLEAN(1); |
281 | | |
282 | | // Compute the trussness of the edges. |
283 | 1.31k | IGRAPH_CHECK(igraph_i_trussness(graph, &support, trussness)); |
284 | 1.31k | igraph_vector_int_destroy(&support); |
285 | 1.31k | IGRAPH_FINALLY_CLEAN(1); |
286 | | |
287 | 1.31k | return IGRAPH_SUCCESS; |
288 | 1.31k | } |