├── CODE_HISTORY.txt ├── Combinatorial ├── community.cpp ├── community.h ├── graph_binary.cpp ├── graph_binary.h └── stability_louvain_LCL.cpp ├── Install_Stability.m ├── Normalised ├── community.cpp ├── community.h ├── graph_binary.cpp ├── graph_binary.h └── stability_louvain_LNL.cpp ├── README.md ├── bin ├── compare_partitions_over_time.m ├── stability.m ├── stability_v1.m └── varinfo.m └── demo ├── Protein_AdK.mat ├── demo.fig ├── demo.mat ├── demo.pdf └── ring_of_rings.mat /CODE_HISTORY.txt: -------------------------------------------------------------------------------- 1 | ###################################### 2 | Version history and bugfixes 3 | ###################################### 4 | 5 | 1.9.2012 - Version 1.0: 6 | Release of the first public version of the stability MATLAB toolbox. 7 | 28.9.2012 8 | Removed unused small code segment in Normalised/community.cpp that could lead to 9 | compilation errors in Windows. 10 | 19.10.2012 11 | Added some header files that can be required for some Linux systems 12 | 13 | ======= 14 | 25.6.2013 - Version 2.0 15 | New version including ability to treat directed graph 16 | 17 | 29.12.2013 18 | -Added some header files that can be required for some Linux systems. 19 | -Added some header files that can be required for Mac Users (Thanks to Adrien Fallou (afallou)). 20 | 21 | [...] 22 | 23 | 22.6.2015 24 | -bugfixes for check of isolated nodes 25 | -------------------------------------------------------------------------------- /Combinatorial/community.cpp: -------------------------------------------------------------------------------- 1 | #include "community.h" 2 | extern "C" { 3 | #include 4 | #include "mex.h" 5 | } 6 | 7 | using namespace std; 8 | 9 | Community::~Community() { 10 | free(g.links); 11 | free(g.weights); 12 | free(g.degrees); 13 | free(g.nb_nodes_per_comm); 14 | } 15 | 16 | Community::Community(double * data, int length_data, int nbp, double minm, 17 | double timet, int type) { 18 | 19 | g = Graph(data, length_data, type); 20 | size = g.nb_nodes; 21 | 22 | neigh_weight.resize(size, -1); 23 | neigh_pos.resize(size); 24 | neigh_last = 0; 25 | 26 | n2c.resize(size); 27 | in.resize(size); 28 | 29 | double temp = 0; 30 | for (int i = 0; i < size; i++) { 31 | n2c[i] = i; 32 | in[i] = g.nb_selfloops(i); 33 | temp += g.weighted_degree(i); 34 | } 35 | 36 | k_mean = (double) temp / g.nb_nodes; 37 | nb_nodes_init = g.nb_nodes; 38 | 39 | nb_pass = nbp; 40 | min_modularity = minm; 41 | 42 | time = timet; 43 | 44 | } 45 | 46 | Community::Community(Graph gc, int nbp, double minm, double timet, int nb_nodes) { 47 | g = gc; 48 | size = g.nb_nodes; 49 | 50 | neigh_weight.resize(size, -1); 51 | neigh_pos.resize(size); 52 | neigh_last = 0; 53 | 54 | time = timet; 55 | 56 | n2c.resize(size); 57 | in.resize(size); 58 | 59 | double temp = 0; 60 | for (int i = 0; i < size; i++) { 61 | n2c[i] = i; 62 | in[i] = g.nb_selfloops(i); 63 | temp += g.weighted_degree(i); 64 | } 65 | nb_nodes_init = nb_nodes; 66 | k_mean = (double) temp / nb_nodes_init; 67 | 68 | nb_pass = nbp; 69 | min_modularity = minm; 70 | } 71 | 72 | void Community::display() { 73 | 74 | vector renumber(size, -1); 75 | for (int node = 0; node < size; node++) { 76 | renumber[n2c[node]]++; 77 | } 78 | 79 | int final = 0; 80 | for (int i = 0; i < size; i++) 81 | if (renumber[i] != -1) 82 | renumber[i] = final++; 83 | 84 | ofstream toto("output.txt", ios::out | ios::app); 85 | toto << "\n\nBegin" << endl; 86 | for (int i = 0; i < size; i++) 87 | toto << i << " " << renumber[n2c[i]] << endl; 88 | toto << "End\n\n" << endl; 89 | } 90 | 91 | // Calculation of the linearised standard stability: R = (1-t)+(1/2m)*t*sum_c*sum_ij(Aij)-sum_c*sum_ij(1/N)^2 92 | double Community::modularity() { 93 | 94 | double q = 1.0 - time; 95 | double m2 = g.total_weight; 96 | for (int i = 0; i < size; i++) { 97 | // It is important not to look at communities where all the 98 | // nodes have been removed in the "one_level" function 99 | if (g.nb_nodes_per_comm[i] > 0) { 100 | q += (time / m2) * (double) in[i] - (g.nb_nodes_per_comm[i] 101 | * g.nb_nodes_per_comm[i]) * (1.0 / nb_nodes_init) * (1.0 102 | / nb_nodes_init); 103 | } 104 | } 105 | return q; 106 | } 107 | 108 | void Community::neigh_comm(unsigned int node) { 109 | for (unsigned int i = 0; i < neigh_last; i++) 110 | neigh_weight[neigh_pos[i]] = -1; 111 | neigh_last = 0; 112 | 113 | pair p = g.neighbors(node); 114 | 115 | unsigned int deg = g.nb_neighbors(node); 116 | 117 | neigh_pos[0] = n2c[node]; 118 | neigh_weight[neigh_pos[0]] = 0; 119 | neigh_last = 1; 120 | 121 | for (unsigned int i = 0; i < deg; i++) { 122 | unsigned int neigh = *(p.first + i); 123 | unsigned int neigh_comm = n2c[neigh]; 124 | double neigh_w = (g.weights == NULL) ? 1. : *(p.second + i); 125 | 126 | if (neigh != node) { 127 | if (neigh_weight[neigh_comm] == -1) { 128 | neigh_weight[neigh_comm] = 0.; 129 | neigh_pos[neigh_last++] = neigh_comm; 130 | } 131 | neigh_weight[neigh_comm] += neigh_w; 132 | } 133 | } 134 | } 135 | 136 | void Community::partition2graph() { 137 | vector renumber(size, -1); 138 | for (int node = 0; node < size; node++) { 139 | renumber[n2c[node]]++; 140 | } 141 | 142 | int final = 0; 143 | for (int i = 0; i < size; i++) 144 | if (renumber[i] != -1) 145 | renumber[i] = final++; 146 | 147 | for (int i = 0; i < size; i++) { 148 | pair p = g.neighbors(i); 149 | 150 | int deg = g.nb_neighbors(i); 151 | for (int j = 0; j < deg; j++) { 152 | int neigh = *(p.first + j); 153 | cout << renumber[n2c[i]] << " " << renumber[n2c[neigh]] << endl; 154 | } 155 | } 156 | } 157 | 158 | void Community::display_partition(char* outfilename) { 159 | ofstream foutput(outfilename, ios::out | ios::app); 160 | 161 | vector renumber(size, -1); 162 | for (int node = 0; node < size; node++) { 163 | renumber[n2c[node]]++; 164 | } 165 | 166 | int final = 0; 167 | for (int i = 0; i < size; i++) 168 | if (renumber[i] != -1) 169 | renumber[i] = final++; 170 | 171 | for (int i = 0; i < size; i++) { 172 | foutput << i << " " << renumber[n2c[i]] << endl; 173 | printf("%d", i); 174 | printf(" "); 175 | printf("%d", renumber[n2c[i]]); 176 | printf("\n"); 177 | } 178 | } 179 | 180 | vector > Community::display_partition2(vector > output) { 181 | 182 | vector renumber(size, -1); 183 | for (int node = 0; node < size; node++) { 184 | renumber[n2c[node]]++; 185 | } 186 | 187 | int final = 0; 188 | for (int i = 0; i < size; i++) 189 | if (renumber[i] != -1) 190 | renumber[i] = final++; 191 | 192 | vector temp(size, 0); 193 | 194 | for (int i = 0; i < size; i++) { 195 | temp[i] = renumber[n2c[i]]; 196 | } 197 | output.push_back(temp); 198 | 199 | return output; 200 | } 201 | 202 | void Community::display_partitioncerr() { 203 | vector renumber(size, -1); 204 | for (int node = 0; node < size; node++) { 205 | renumber[n2c[node]]++; 206 | } 207 | 208 | int final = 0; 209 | for (int i = 0; i < size; i++) 210 | if (renumber[i] != -1) 211 | renumber[i] = final++; 212 | 213 | for (int i = 0; i < size; i++) 214 | cerr << i << " " << renumber[n2c[i]] << endl; 215 | } 216 | 217 | // This function is not so nice 218 | // malloc is dirty 219 | Graph Community::partition2graph_binary() { 220 | 221 | vector renumber(size, -1); 222 | for (int node = 0; node < size; node++) { 223 | renumber[n2c[node]]++; 224 | } 225 | 226 | int final = 0; 227 | for (int i = 0; i < size; i++) 228 | if (renumber[i] != -1) 229 | renumber[i] = final++; 230 | 231 | // Compute communities 232 | vector > comm_nodes(final); 233 | for (int node = 0; node < size; node++) { 234 | comm_nodes[renumber[n2c[node]]].push_back(node); 235 | } 236 | 237 | // unweigthed to weighted 238 | Graph g2; 239 | g2.nb_nodes = comm_nodes.size(); 240 | g2.degrees = (unsigned long *) malloc(comm_nodes.size() 241 | * sizeof(unsigned long)); 242 | g2.links = (unsigned int *) malloc((long) g.nb_links * 2 243 | * sizeof(unsigned int)); 244 | g2.weights = (double *) malloc((long) g.nb_links * 2 * sizeof(double)); 245 | 246 | long where = 0; 247 | int comm_deg = comm_nodes.size(); 248 | 249 | // Backup of the number of nodes in each community 250 | g2.nb_nodes_per_comm = (int *) malloc(comm_deg * sizeof(int)); 251 | 252 | for (int comm = 0; comm < comm_deg; comm++) { 253 | map m; 254 | map::iterator it; 255 | 256 | int comm_size = comm_nodes[comm].size(); 257 | for (int node = 0; node < comm_size; node++) { 258 | pair p = g.neighbors( 259 | comm_nodes[comm][node]); 260 | ; 261 | int deg = g.nb_neighbors(comm_nodes[comm][node]); 262 | for (int i = 0; i < deg; i++) { 263 | int neigh = *(p.first + i); 264 | int neigh_comm = renumber[n2c[neigh]]; 265 | double neigh_weight = (g.weights == NULL) ? 1 : *(p.second + i); 266 | 267 | it = m.find(neigh_comm); 268 | if (it == m.end()) 269 | m.insert(make_pair(neigh_comm, neigh_weight)); 270 | else 271 | it->second += neigh_weight; 272 | } 273 | } 274 | 275 | g2.degrees[comm] = (comm == 0) ? m.size() : g2.degrees[comm - 1] 276 | + m.size(); 277 | g2.nb_links += m.size(); 278 | 279 | for (it = m.begin(); it != m.end(); it++) { 280 | g2.total_weight += it->second; 281 | g2.links[where] = it->first; 282 | g2.weights[where] = it->second; 283 | where++; 284 | } 285 | 286 | // Initialisation 287 | g2.nb_nodes_per_comm[comm] = 0; 288 | 289 | } 290 | 291 | // Transfer of the number of nodes per community to the new graph 292 | for (unsigned int n = 0; n < g.nb_nodes; n++) { 293 | g2.nb_nodes_per_comm[renumber[n2c[n]]] = g.nb_nodes_per_comm[n2c[n]]; 294 | } 295 | 296 | g2.links = (unsigned int*) realloc(g2.links, (long) g2.nb_links * 2 297 | * sizeof(unsigned int)); 298 | g2.weights = (double*) realloc(g2.weights, (long) g2.nb_links * 2 299 | * sizeof(double)); 300 | 301 | return g2; 302 | } 303 | 304 | bool Community::one_level() { 305 | 306 | bool improvement = false; 307 | int nb_pass_done = 0; 308 | int nb_moves = 0; 309 | double new_mod = modularity(); 310 | double cur_mod = new_mod; 311 | 312 | // repeat while 313 | // there is an improvement of modularity 314 | // or there is an improvement of modularity greater than a given epsilon 315 | // or a predefined number of pass have been done 316 | 317 | vector random_order(size); 318 | for (int i = 0; i < size; i++) 319 | random_order[i] = i; 320 | for (int i = 0; i < size - 1; i++) { 321 | int rand_pos = rand() % (size - i) + i; 322 | int tmp = random_order[i]; 323 | random_order[i] = random_order[rand_pos]; 324 | random_order[rand_pos] = tmp; 325 | } 326 | 327 | // The original number of nodes per communities must be saved or it will be lost. 328 | int *nb_nodes_per_comm_temp = new int[g.nb_nodes]; 329 | for (unsigned int k = 0; k < g.nb_nodes; k++) 330 | nb_nodes_per_comm_temp[k] = g.nb_nodes_per_comm[k]; 331 | 332 | do { 333 | 334 | cur_mod = new_mod; 335 | nb_moves = 0; 336 | nb_pass_done++; 337 | 338 | // for each node: remove the node from its community and insert it in the best community 339 | for (int node_tmp = 0; node_tmp < size; node_tmp++) { 340 | //int node = node_tmp; 341 | int node = random_order[node_tmp]; 342 | int node_comm = n2c[node]; 343 | 344 | // computation of all neighboring communities of current node 345 | neigh_comm(node); 346 | 347 | // remove node from its current community 348 | remove(node, node_comm, neigh_weight[node_comm], 349 | nb_nodes_per_comm_temp); 350 | 351 | // compute the nearest community for node 352 | // default choice for future insertion is the former community 353 | int best_comm = node_comm; 354 | double best_nblinks = 0; 355 | double best_increase = 0.; 356 | for (unsigned int i = 0; i < neigh_last; i++) { 357 | // The original number of nodes per community has to be in the argument 358 | double increase = modularity_gain(node, neigh_pos[i], 359 | neigh_weight[neigh_pos[i]], nb_nodes_per_comm_temp); 360 | if (increase > best_increase) { 361 | best_comm = neigh_pos[i]; 362 | best_nblinks = neigh_weight[neigh_pos[i]]; 363 | best_increase = increase; 364 | } 365 | } 366 | 367 | // insert node in the nearest community 368 | insert(node, best_comm, best_nblinks, nb_nodes_per_comm_temp); 369 | 370 | if (best_comm != node_comm) 371 | nb_moves++; 372 | } 373 | 374 | double total_in = 0; 375 | for (unsigned int i = 0; i < in.size(); i++) { 376 | total_in += in[i]; 377 | } 378 | 379 | new_mod = modularity(); 380 | 381 | if (nb_moves > 0) 382 | improvement = true; 383 | 384 | if (new_mod > 1) { 385 | printf( 386 | "\tThe louvain algorithm appears to be stuck in a loop. \n\tPlease first make sure that the adjacency matrix is perfectly symmetric \n\tand, if it is, try to increase the precision.\n"); 387 | for (int i = 0; i < size; i++) 388 | n2c[i] = 0; 389 | nb_pass = -10; 390 | delete[] nb_nodes_per_comm_temp; 391 | return false; 392 | } 393 | 394 | } while (nb_moves > 0 && new_mod - cur_mod > min_modularity); 395 | 396 | delete[] nb_nodes_per_comm_temp; 397 | 398 | return improvement; 399 | } 400 | 401 | -------------------------------------------------------------------------------- /Combinatorial/community.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef COMMUNITY_H 3 | #define COMMUNITY_H 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "graph_binary.h" 14 | 15 | #if defined(_WIN32) || defined(WIN32) || defined(__WIN32__) || defined(_WIN64) || defined(WIN64) || defined(__WIN64__) || defined(_WINDOWS) || defined(__WINDOWS__) 16 | #define __WIN__ 17 | #endif 18 | 19 | #ifdef __WIN__ 20 | #include 21 | #endif 22 | 23 | using namespace std; 24 | 25 | class Community { 26 | public: 27 | vector neigh_weight; 28 | vector neigh_pos; 29 | unsigned int neigh_last; 30 | 31 | // network to compute communities for 32 | Graph g; 33 | 34 | // nummber of nodes in the network and size of all vectors 35 | int size; 36 | 37 | // community to which each node belongs 38 | vector n2c; 39 | 40 | // used to compute the modularity participation of each community 41 | vector in; 42 | 43 | double k_mean; 44 | 45 | // number of pass for one level computation 46 | // if -1, compute as many pass as needed to increase modularity 47 | int nb_pass; 48 | 49 | // saves the initial number of nodes. 50 | int nb_nodes_init; 51 | 52 | 53 | 54 | // a new pass is computed if the last one has generated an increase 55 | // greater than min_modularity 56 | // if 0. even a minor increase is enough to go for one more pass 57 | double min_modularity; 58 | 59 | double time; 60 | 61 | 62 | // constructors: 63 | // reads graph from file using graph constructor 64 | // type defined the weighted/unweighted status of the graph file 65 | Community (char *filename, int type, int nb_pass, double min_modularity, double t); 66 | // copy graph 67 | Community (Graph g, int nb_pass, double min_modularity, double t, int nb_nodes); 68 | 69 | Community (double * data, int length_data, int nbp, double minm, double timet, int type); 70 | 71 | ~Community(); 72 | 73 | // display the community of each node 74 | void display(); 75 | 76 | // remove the node from its current community with which it has dnodecomm links 77 | inline void remove(int node, int comm, double dnodecomm, int nb_nodes_per_comm_temp[]); 78 | 79 | // insert the node in comm with which it shares dnodecomm links 80 | inline void insert(int node, int comm, double dnodecomm, int nb_nodes_per_comm_temp[]); 81 | 82 | // compute the gain of stability if node where inserted in comm 83 | // containing the original number of nodes in each community. 84 | inline double modularity_gain(int node, int comm, double dnodecomm, int nb_nodes_per_comm_temp[]); 85 | 86 | // compute the set of neighboring communities of node 87 | // for each community, gives the number of links from node to comm 88 | void neigh_comm(unsigned int node); 89 | 90 | // compute the modularity of the current partition 91 | double modularity(); 92 | 93 | // displays the graph of communities as computed by one_level 94 | void partition2graph(); 95 | // displays the current partition (with communities renumbered from 0 to k-1) 96 | void display_partition(char *outfilename); 97 | 98 | 99 | vector >display_partition2(vector > output); 100 | 101 | // displays the current partition (with communities renumbered from 0 to k-1) on cerr 102 | void display_partitioncerr(); 103 | 104 | // generates the binary graph of communities as computed by one_level 105 | Graph partition2graph_binary(); 106 | 107 | // compute communities of the graph for one level 108 | // return the modularity 109 | bool one_level(); 110 | }; 111 | 112 | inline void 113 | Community::remove(int node, int comm, double dnodecomm, int nb_nodes_per_comm_temp[]) { 114 | assert(node>=0 && node necessary to have a temporary variable 122 | // nb_nodes_per_comm_temp. 123 | 124 | in[comm] -= 2*dnodecomm + g.nb_selfloops(node); 125 | n2c[node] = -1; 126 | g.nb_nodes_per_comm[comm] -= nb_nodes_per_comm_temp[node]; 127 | } 128 | 129 | inline void 130 | Community::insert(int node, int comm, double dnodecomm, int nb_nodes_per_comm_temp[]) { 131 | assert(node>=0 && node=0 && node 2 | #include "graph_binary.h" 3 | #include "math.h" 4 | extern "C" { 5 | #include 6 | #include "mex.h" 7 | #include "matrix.h" 8 | } 9 | 10 | #if defined(macintosh) || defined(MACOS) || defined(_MACOS) || defined(__APPLE__) || defined(_MAC) || defined(MAC) || defined(mac) || defined(MACINTOSH) 11 | #define __MAC__ 12 | #endif 13 | 14 | #if defined(_WIN32) || defined(WIN32) || defined(__WIN32__) || defined(_WIN64) || defined(WIN64) || defined(__WIN64__) || defined(_WINDOWS) || defined(__WINDOWS__) 15 | #define __WIN__ 16 | #endif 17 | 18 | #if defined(linux) || defined(__linux__) || defined(_linux) || defined(LINUX) || defined(_LINUX) || defined(_UNIX) || defined(__UNIX__) || defined(__gnu_linux__) || defined(__unix__) || defined(UNIX) || defined(unix) || defined(sparc) 19 | #define __lin__ 20 | #endif 21 | 22 | #ifdef __WIN__ 23 | #endif 24 | 25 | #ifdef __lin__ 26 | #include 27 | #endif 28 | 29 | #ifdef __MAC__ 30 | #include 31 | #endif 32 | 33 | Graph::Graph() { 34 | nb_nodes = 0; 35 | nb_links = 0; 36 | total_weight = 0; 37 | } 38 | 39 | Graph::Graph(char *filename, int type) { 40 | ifstream finput; 41 | finput.open(filename, fstream::in | fstream::binary); 42 | 43 | // read number of nodes on 4 bytes 44 | finput.read((char *) &nb_nodes, 4); 45 | 46 | degrees = (unsigned long *) malloc((long) nb_nodes * sizeof(long)); 47 | finput.read((char *) degrees, (long) nb_nodes * sizeof(int)); 48 | 49 | // read links: 4 bytes for each link (each link is counted twice) 50 | nb_links = degrees[nb_nodes - 1] / 2; 51 | links = (unsigned int *) malloc((long) nb_links * 2 * sizeof(int)); 52 | finput.read((char *) links, (long) nb_links * 2 * sizeof(int)); 53 | 54 | // IF WEIGHTED : read weights: 4 bytes for each link (each link is counted twice) 55 | if (type == WEIGHTED) { 56 | weights = (double *) malloc((long) nb_links * 8); 57 | finput.read((char *) weights, (long) nb_links * 8); 58 | total_weight = 0; 59 | for (unsigned int i = 0; i < nb_links * 2; i++) { 60 | total_weight += weights[i]; 61 | } 62 | } else { 63 | weights = NULL; 64 | total_weight = 2 * nb_links; 65 | } 66 | 67 | // New attribute being the backup of the number of nodes per community 68 | nb_nodes_per_comm = (int *) malloc((long) nb_nodes * 4); 69 | for (unsigned int i = 0; i < nb_nodes; i++) 70 | nb_nodes_per_comm[i] = 1; 71 | 72 | } 73 | 74 | Graph::Graph(double * data, int length_data, int type) { 75 | 76 | nb_nodes = int(data[length_data - 1]) + 1; 77 | 78 | degrees = (unsigned long *) malloc((long) nb_nodes * sizeof(unsigned long)); 79 | 80 | int tmp_node = -1; 81 | int j = 0; 82 | int tot = 0; 83 | for (int i = 0; i < length_data; i++) { 84 | tot += 1; 85 | if (int(data[i]) == tmp_node) 86 | degrees[tmp_node] += 1; 87 | else { 88 | tmp_node++; 89 | degrees[tmp_node] = tot; 90 | } 91 | } 92 | 93 | nb_links = degrees[nb_nodes - 1] / 2; 94 | 95 | links = (unsigned int *) malloc((long) nb_links * 2 * sizeof(unsigned int)); 96 | for (int i = 0; i < length_data; i++) { 97 | links[i] = int(data[length_data + i]); 98 | } 99 | 100 | // IF WEIGHTED : read weights: 4 bytes for each link (each link is counted twice) 101 | weights = NULL; 102 | total_weight = length_data; 103 | if (type == WEIGHTED) { 104 | weights = (double *) malloc((long) nb_links * 2 * sizeof(double));//new double[nb_links]; 105 | total_weight = 0; 106 | for (unsigned int i = 0; i < nb_links * 2; i++) { 107 | weights[i] = (double) data[2 * length_data + i]; 108 | total_weight = total_weight + weights[i]; 109 | } 110 | } 111 | 112 | // New attribute being the backup of the number of nodes per community 113 | nb_nodes_per_comm = (int *) malloc((long) nb_nodes * sizeof(int)); 114 | for (unsigned int i = 0; i < nb_nodes; i++) 115 | nb_nodes_per_comm[i] = 1; 116 | 117 | } 118 | 119 | void Graph::display() { 120 | for (unsigned int node = 0; node < nb_nodes; node++) { 121 | pair p = neighbors(node); 122 | for (unsigned int i = 0; i < nb_neighbors(node); i++) { 123 | if (node <= *(p.first + i)) { 124 | if (weights != NULL) 125 | cout << node << " " << *(p.first + i) << " " << *(p.second 126 | + i) << endl; 127 | else 128 | cout << node << " " << *(p.first + i) << endl; 129 | } 130 | } 131 | } 132 | } 133 | 134 | void Graph::display_reverse() { 135 | for (unsigned int node = 0; node < nb_nodes; node++) { 136 | pair p = neighbors(node); 137 | for (unsigned int i = 0; i < nb_neighbors(node); i++) { 138 | if (node > *(p.first + i)) { 139 | if (weights != NULL) 140 | cout << *(p.first + i) << " " << node << " " << *(p.second 141 | + i) << endl; 142 | else 143 | cout << *(p.first + i) << " " << node << endl; 144 | } 145 | } 146 | } 147 | } 148 | 149 | bool Graph::check_symmetry() { 150 | int error = 0; 151 | for (unsigned int node = 0; node < nb_nodes; node++) { 152 | pair p = neighbors(node); 153 | for (unsigned int i = 0; i < nb_neighbors(node); i++) { 154 | unsigned int neigh = *(p.first + i); 155 | double weight = *(p.second + i); 156 | 157 | pair p_neigh = neighbors(neigh); 158 | for (unsigned int j = 0; j < nb_neighbors(neigh); j++) { 159 | unsigned int neigh_neigh = *(p_neigh.first + j); 160 | double neigh_weight = *(p_neigh.second + j); 161 | 162 | if (node == neigh_neigh && weight != neigh_weight) { 163 | cout << node << " " << neigh << " " << weight << " " 164 | << neigh_weight << endl; 165 | if (error++ == 10) 166 | exit(0); 167 | } 168 | } 169 | } 170 | } 171 | return (error == 0); 172 | } 173 | 174 | void Graph::display_binary(char *outfile) { 175 | ofstream foutput; 176 | foutput.open(outfile, fstream::out | fstream::binary); 177 | 178 | foutput.write((char *) (&nb_nodes), 4); 179 | foutput.write((char *) (degrees), 4 * nb_nodes); 180 | foutput.write((char *) (links), 8 * nb_links); 181 | } 182 | -------------------------------------------------------------------------------- /Combinatorial/graph_binary.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | #if defined(macintosh) || defined(MACOS) || defined(_MACOS) || defined(__APPLE__) || defined(_MAC) || defined(MAC) || defined(mac) || defined(MACINTOSH) 4 | #define __MAC__ 5 | #endif 6 | 7 | 8 | #if defined(_WIN32) || defined(WIN32) || defined(__WIN32__) || defined(_WIN64) || defined(WIN64) || defined(__WIN64__) || defined(_WINDOWS) || defined(__WINDOWS__) 9 | #define __WIN__ 10 | #endif 11 | 12 | #if defined(linux) || defined(__linux__) || defined(_linux) || defined(LINUX) || defined(_LINUX) || defined(_UNIX) || defined(__UNIX__) || defined(__gnu_linux__) || defined(__unix__) || defined(UNIX) || defined(unix) || defined(sparc) 13 | #define __lin__ 14 | #endif 15 | 16 | #ifndef GRAPH_H 17 | #define GRAPH_H 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #ifdef __lin__ 31 | #include 32 | #endif 33 | 34 | #ifdef __WIN__ 35 | #include 36 | #endif 37 | 38 | #ifdef __MAC__ 39 | #include 40 | #endif 41 | 42 | 43 | #define WEIGHTED 0 44 | #define UNWEIGHTED 1 45 | 46 | using namespace std; 47 | 48 | class Graph { 49 | public: 50 | unsigned int nb_nodes; 51 | unsigned int nb_links; 52 | double long total_weight; 53 | 54 | 55 | unsigned long *degrees; 56 | unsigned int *links; 57 | double *weights; 58 | // It is important to save at each step the number of nodes in each community. 59 | int *nb_nodes_per_comm; 60 | 61 | Graph(); 62 | 63 | // binary file format is 64 | // 4 bytes for the number of nodes in the graph 65 | // 4*(nb_nodes) bytes for the cumulative degree for each node: 66 | // deg(0)=degrees[0] 67 | // deg(k)=degrees[k]-degrees[k-1] 68 | // 4*(sum_degrees) bytes for the links 69 | // IF WEIGHTED 4*(sum_degrees) bytes for the weights 70 | Graph(char *filename, int type); 71 | 72 | Graph(int nb_nodes, int nb_links, int total_weight, int *degrees, int *links, int *weights); 73 | 74 | Graph(double * data, int length_data, int type); 75 | 76 | Graph(int n1, int k1, int n2, int k2, int n3, int k3); 77 | Graph(int n1, int k1, int n2, int k2); 78 | 79 | void display(void); 80 | void display_reverse(void); 81 | void display_binary(char *outfile); 82 | bool check_symmetry(); 83 | 84 | // return the number of neighbors (degree) of the node 85 | inline unsigned int nb_neighbors(unsigned int node); 86 | 87 | // return the number of self loops of the node 88 | inline double nb_selfloops(unsigned int node); 89 | 90 | // return the weighted degree of the node 91 | inline double weighted_degree(unsigned int node); 92 | 93 | // return pointers to the first neighbor and first weight of the node 94 | inline pair neighbors(unsigned int node); 95 | }; 96 | 97 | 98 | inline unsigned int 99 | Graph::nb_neighbors(unsigned int node) { 100 | assert(node>=0 && node=0 && node p = neighbors(node); 114 | for (unsigned int i=0 ; i=0 && node p = neighbors(node); 133 | double res = 0; 134 | for (unsigned int i=0 ; i 142 | Graph::neighbors(unsigned int node) { 143 | assert(node>=0 && node 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #ifdef __WIN__ 25 | #include 26 | #include 27 | #include 28 | #include 29 | #endif 30 | 31 | #include "graph_binary.h" 32 | #include "community.h" 33 | extern "C" { 34 | #include 35 | #include "mex.h" 36 | #include "matrix.h" 37 | 38 | } 39 | 40 | #ifdef __WIN__ 41 | int gettimeofday (struct timeval *tp, void *tz) 42 | { 43 | struct _timeb timebuffer; 44 | _ftime (&timebuffer); 45 | tp->tv_sec = timebuffer.time; 46 | tp->tv_usec = timebuffer.millitm * 1000; 47 | return 0; 48 | } 49 | #endif 50 | 51 | #ifdef __lin__ 52 | #include 53 | #include 54 | #include 55 | #endif 56 | 57 | #ifdef __MAC__ 58 | #include 59 | #include 60 | #include 61 | #endif 62 | 63 | using namespace std; 64 | 65 | double *data = NULL; 66 | int type = WEIGHTED; 67 | int nb_pass = 0; 68 | double precision = 0.000001; 69 | int display_level = -1; 70 | int length_data = -1; 71 | double timet = 1.0; 72 | bool hierarchy = false; 73 | 74 | bool parse_arg(int nrhs, const mxArray *prhs[]) { 75 | 76 | if (nrhs > 0) { 77 | if (mxGetN(prhs[0]) != 3 && mxGetN(prhs[0]) != 2) { 78 | printf("N=%d", mxGetN(prhs[0])); 79 | return false; 80 | } 81 | data = (double *) mxGetPr(prhs[0]); 82 | length_data = mxGetM(prhs[0]); 83 | } 84 | if (nrhs > 1) { 85 | timet = ((double) mxGetScalar(prhs[1])); 86 | } 87 | if (nrhs > 2) { 88 | if (precision > 1) 89 | return false; 90 | precision = ((double) mxGetScalar(prhs[2])); 91 | } 92 | if (nrhs > 3) { 93 | double p = (double) mxGetScalar(prhs[3]); 94 | if (p == 119) { 95 | if (mxGetN(prhs[0]) != 3) 96 | return false; 97 | type = WEIGHTED; 98 | } else if (p == 117) { 99 | type = UNWEIGHTED; 100 | } else { 101 | return false; 102 | } 103 | } 104 | if (nrhs > 4) { 105 | double p = (double) mxGetScalar(prhs[4]); 106 | if (p == 104) { 107 | hierarchy = true; 108 | } else if (p == 110) { 109 | hierarchy = false; 110 | } else { 111 | return false; 112 | } 113 | } 114 | if (nrhs > 5 || nrhs < 1) { 115 | return false; 116 | } 117 | return true; 118 | } 119 | 120 | extern "C" { 121 | void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { 122 | 123 | struct timeval tv; 124 | gettimeofday(&tv, NULL); 125 | 126 | // Initialize the random generator 127 | srand(((tv.tv_sec * 1000) + (tv.tv_usec / 1000)) * getpid()); 128 | 129 | //parse arguments 130 | if (parse_arg(nrhs, prhs) && nlhs < 4) { 131 | 132 | // Vector containing the hierarchy of the partitioning 133 | vector > output; 134 | 135 | // Creation of the Community object 136 | Community *c = new Community(data, length_data, -1, precision, timet, 137 | type); 138 | 139 | // Initial number of nodes 140 | int numberinitial = (*c).g.nb_nodes; 141 | 142 | // Calculation of the initial modularity 143 | double mod = (*c).modularity(); 144 | 145 | // First partitioning 146 | bool improvement = (*c).one_level(); 147 | 148 | // Calculation of its modularity 149 | double new_mod = (*c).modularity(); 150 | 151 | output = (*c).display_partition2(output); 152 | 153 | Graph g = (*c).partition2graph_binary(); 154 | 155 | int numberofcommunities = (*c).g.nb_nodes; 156 | int level = 0; 157 | while (improvement) { 158 | 159 | mod = new_mod; 160 | 161 | delete c; 162 | c = new Community(g, -1, precision, timet, numberinitial); 163 | 164 | numberofcommunities = (*c).g.nb_nodes; 165 | 166 | improvement = (*c).one_level(); 167 | 168 | if ((*c).nb_pass != -10) 169 | new_mod = (*c).modularity(); 170 | else 171 | new_mod = 0; 172 | 173 | output = (*c).display_partition2(output); 174 | 175 | g = (*c).partition2graph_binary(); 176 | level++; 177 | 178 | } 179 | 180 | numberofcommunities = (*c).g.nb_nodes; 181 | free(g.links); 182 | free(g.weights); 183 | free(g.degrees); 184 | free(g.nb_nodes_per_comm); 185 | 186 | plhs[0] = mxCreateDoubleMatrix(1, 1, mxREAL); //mxReal is our data-type 187 | 188 | //Get a pointer to the data space in our newly allocated memory 189 | double * out1 = (double*) mxGetPr(plhs[0]); 190 | 191 | out1[0] = new_mod; 192 | 193 | if (nlhs > 1) { 194 | 195 | plhs[1] = mxCreateDoubleMatrix(1, 1, mxREAL); //mxReal is our data-type 196 | 197 | //Get a pointer to the data space in our newly allocated memory 198 | double * out2 = (double*) mxGetPr(plhs[1]); 199 | 200 | out2[0] = numberofcommunities; 201 | 202 | } 203 | 204 | if (hierarchy && nlhs > 2) { 205 | int length_output = 0; 206 | for (int unsigned i = 0; i < output.size(); i++) 207 | length_output += output[i].size(); 208 | plhs[2] = mxCreateDoubleMatrix(length_output, 2, mxREAL); 209 | double * output_tab = (double*) mxGetPr(plhs[2]); 210 | double counter_temp = 0; 211 | int counter_temp2 = 0; 212 | for (int i = 0; i < output.size(); i++) { 213 | counter_temp = 0; 214 | for (int unsigned j = 0; j < output[i].size(); j++) { 215 | 216 | output_tab[counter_temp2] = (double) counter_temp; 217 | output_tab[counter_temp2 + length_output] 218 | = (double) output[i][j]; 219 | counter_temp2++; 220 | counter_temp++; 221 | } 222 | } 223 | } else if (nlhs > 2) { 224 | 225 | vector n2c(numberinitial); 226 | 227 | for (unsigned int i = 0; i < numberinitial; i++) 228 | n2c[i] = i; 229 | 230 | for (int l = 0; l < output.size(); l++) { 231 | for (unsigned int node = 0; node < numberinitial; node++) { 232 | n2c[node] = output[l][n2c[node]]; 233 | } 234 | } 235 | 236 | plhs[2] = mxCreateDoubleMatrix(numberinitial, 1, mxREAL); 237 | double * output_tab = (double*) mxGetPr(plhs[2]); 238 | for (unsigned int node = 0; node < numberinitial; node++) { 239 | output_tab[node] = (double) n2c[node]; 240 | 241 | } 242 | } 243 | 244 | delete c; 245 | 246 | } else { 247 | printf("\n\nSYNTAX:"); 248 | 249 | printf("\n\n\t[stability]=stability_louvain_LCL(graph)"); 250 | printf("\n\t[stability]=stability_louvain_LCL(graph, markov time)"); 251 | printf( 252 | "\n\t[stability]=stability_louvain_LCL(graph, markov time, precision)"); 253 | printf( 254 | "\n\t[stability]=stability_louvain_LCL(graph, markov time, precision, weighted)"); 255 | printf( 256 | "\n\t[stability]=stability_louvain_LCL(graph, markov time, precision, weighted, hierarchy)"); 257 | printf( 258 | "\n\t[stability, number of communities]=stability_louvain_LCL(graph)"); 259 | printf( 260 | "\n\t[stability, number of communities]=stability_louvain_LCL(graph, markov time)"); 261 | printf( 262 | "\n\t[stability, number of communities]=stability_louvain_LCL(graph, markov time, precision)"); 263 | printf( 264 | "\n\t[stability, number of communities]=stability_louvain_LCL(graph, markov time, precision, weighted)"); 265 | printf( 266 | "\n\t[stability, number of communities]=stability_louvain_LCL(graph, markov time, precision, weighted, hierarchy)"); 267 | printf( 268 | "\n\t[stability, number of communities, communities]=stability_louvain_LCL(graph)"); 269 | printf( 270 | "\n\t[stability, number of communities, communities]=stability_louvain_LCL(graph, markov time)"); 271 | printf( 272 | "\n\t[stability, number of communities, communities]=stability_louvain_LCL(graph, markov time, precision)"); 273 | printf( 274 | "\n\t[stability, number of communities, communities]=stability_louvain_LCL(graph, markov time, precision, weighted)"); 275 | printf( 276 | "\n\t[stability, number of communities, communities]=stability_louvain_LCL(graph, markov time, precision, weighted, hierarchy)"); 277 | 278 | printf("\n\nDESCRIPTION:"); 279 | 280 | printf("\n\n Inputs:"); 281 | printf( 282 | "\n\n\t - graph\t list of edges in the form [node, node, weight;node, node, weight;...] \n\t\t\t if the graph is weighted or [node, node;node, node;...] if the graph is unweighted."); 283 | printf( 284 | "\n\t - markov time\t time allowed to the random walkers to explore the graph \n\t\t\t (default: 1.0, corresponding to the calculation of the modularity)"); 285 | printf( 286 | "\n\t - precision\t required precision for the stability (default: 0.000001)"); 287 | printf( 288 | "\n\t - weighted\t put \'w\' if the graph is weighted or \'u\' if the graph is unweighted (default: unweighted)"); 289 | printf( 290 | "\n\t - hierarchy\t put \'h\' if the ''communities'' output should be the hierarchy of \n\t\t\t the partitions or \'n\' if it should only be the final partition (default: non-hiearchy)"); 291 | 292 | printf("\n\n Outputs:"); 293 | printf( 294 | "\n\n\t - stability\t\t\t value of the stability of the final partition."); 295 | printf( 296 | "\n\t - number of communities\t number of communities of the final partition."); 297 | printf( 298 | "\n\t - communities\t\t\t affectation of the nodes to the communities either in \n\t\t\t\t\t the final partition (if \'hierarchy\'==\'u\') or in the hierarchy of partitions (if \'hierarchy\'==\'h\')."); 299 | printf("\n\n\n"); 300 | 301 | plhs[0] = mxCreateDoubleMatrix(0, 0, mxREAL); 302 | 303 | if (nlhs > 1) 304 | plhs[1] = mxCreateDoubleMatrix(0, 0, mxREAL); 305 | 306 | if (nlhs > 2) 307 | plhs[2] = mxCreateDoubleMatrix(0, 0, mxREAL); 308 | } 309 | 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /Install_Stability.m: -------------------------------------------------------------------------------- 1 | 2 | disp(' '); 3 | disp('Building of combinatorial laplacian mex files...'); 4 | 5 | cd('Combinatorial'); 6 | 7 | mex stability_louvain_LCL.cpp community.cpp graph_binary.cpp; 8 | 9 | cd ..; 10 | 11 | disp('Building of normalised laplacian mex files...'); 12 | 13 | cd('Normalised'); 14 | 15 | mex stability_louvain_LNL.cpp community.cpp graph_binary.cpp; 16 | 17 | cd ..; 18 | 19 | disp('Moving build files to bin directory...'); 20 | 21 | cd('Combinatorial'); 22 | files=dir('stability_louvain_LCL.mex*'); 23 | for i=1:length(files) 24 | copyfile(files(i).name,'../bin'); 25 | end 26 | 27 | cd('../Normalised'); 28 | files=dir('stability_louvain_LNL.mex*'); 29 | for i=1:length(files) 30 | copyfile(files(i).name,'../bin'); 31 | end 32 | 33 | disp(' '); 34 | disp('Do you wish to add stability to your Matlab path?'); 35 | disp('This will allow you to execute stability as a standard Matlab function.'); 36 | reply = input('Y/N [Y]: ', 's'); 37 | 38 | 39 | while ~strcmpi(reply(1),'Y') && ~strcmpi(reply(1),'N') 40 | reply = input('Please answer by ''Y'' or ''N''. Do you wish to add stability to your Matlab path? Y/N [Y]: ', 's'); 41 | end 42 | 43 | if strcmpi(reply(1),'Y') 44 | cd('../bin'); 45 | path(path,pwd); 46 | output_savepath = savepath; 47 | if ~output_savepath 48 | disp(' '); 49 | disp('Stability binaries sucessfully added to your Matlab path.'); 50 | else 51 | warning('There was a problem adding stability to your Matlab path. You may need to run Matlab as a superuser. Alternatively you can save your path to a different location by calling SAVEPATH with an input argument that specifies the fullpath. For MATLAB to use that path in future sessions, save the path to ''pathdef.m'' in your MATLAB startup folder. '); 52 | end 53 | end 54 | 55 | cd ..; 56 | disp(' '); 57 | disp('Done'); 58 | -------------------------------------------------------------------------------- /Normalised/community.cpp: -------------------------------------------------------------------------------- 1 | #include "community.h" 2 | extern "C" { 3 | #include 4 | #include "mex.h" 5 | } 6 | 7 | using namespace std; 8 | 9 | Community::~Community() { 10 | free(g.links); 11 | free(g.weights); 12 | free(g.degrees); 13 | } 14 | 15 | Community::Community(char * filename, char * filename_w, int type, int nbp, 16 | double minm) { 17 | g = Graph(filename, filename_w, type); 18 | size = g.nb_nodes; 19 | 20 | neigh_weight.resize(size, -1); 21 | neigh_pos.resize(size); 22 | neigh_last = 0; 23 | 24 | n2c.resize(size); 25 | in.resize(size); 26 | tot.resize(size); 27 | 28 | for (int i = 0; i < size; i++) { 29 | n2c[i] = i; 30 | in[i] = g.nb_selfloops(i); 31 | tot[i] = g.weighted_degree(i); 32 | } 33 | 34 | nb_pass = nbp; 35 | min_modularity = minm; 36 | 37 | } 38 | 39 | Community::Community(double * data, int length_data, int nbp, double minm, 40 | double timet, int type) { 41 | 42 | g = Graph(data, length_data, type); 43 | size = g.nb_nodes; 44 | 45 | neigh_weight.resize(size, -1); 46 | neigh_pos.resize(size); 47 | neigh_last = 0; 48 | 49 | n2c.resize(size); 50 | in.resize(size); 51 | tot.resize(size); 52 | 53 | for (int i = 0; i < size; i++) { 54 | n2c[i] = i; 55 | in[i] = g.nb_selfloops(i); 56 | tot[i] = g.weighted_degree(i); 57 | } 58 | 59 | nb_pass = nbp; 60 | min_modularity = minm; 61 | 62 | time = timet; 63 | 64 | } 65 | 66 | Community::Community(Graph gc, int nbp, double minm, double timet) { 67 | g = gc; 68 | size = g.nb_nodes; 69 | 70 | //cout << "SIZE = " << size << endl; 71 | neigh_weight.resize(size, -1); 72 | neigh_pos.resize(size); 73 | neigh_last = 0; 74 | 75 | n2c.resize(size); 76 | in.resize(size); 77 | tot.resize(size); 78 | 79 | for (int i = 0; i < size; i++) { 80 | n2c[i] = i; 81 | in[i] = g.nb_selfloops(i); 82 | tot[i] = g.weighted_degree(i); 83 | } 84 | 85 | nb_pass = nbp; 86 | min_modularity = minm; 87 | 88 | time = timet; 89 | } 90 | 91 | void Community::display() { 92 | cerr << endl << "<"; 93 | for (int i = 0; i < size; i++) 94 | cerr << " " << i << "/" << n2c[i] << "/" << in[i] << "/" << tot[i]; 95 | cerr << ">" << endl; 96 | } 97 | 98 | double Community::modularity() { 99 | double q = 1.0 - time; 100 | double m2 = (double) g.total_weight; 101 | 102 | for (int i = 0; i < size; i++) { 103 | if (tot[i] > 0) 104 | q += time * (double) in[i] / m2 - ((double) tot[i] / m2) 105 | * ((double) tot[i] / m2); 106 | } 107 | 108 | return q; 109 | } 110 | 111 | void Community::neigh_comm(unsigned int node) { 112 | for (unsigned int i = 0; i < neigh_last; i++) 113 | neigh_weight[neigh_pos[i]] = -1; 114 | neigh_last = 0; 115 | 116 | pair p = g.neighbors(node); 117 | 118 | unsigned int deg = g.nb_neighbors(node); 119 | 120 | neigh_pos[0] = n2c[node]; 121 | neigh_weight[neigh_pos[0]] = 0; 122 | neigh_last = 1; 123 | 124 | for (unsigned int i = 0; i < deg; i++) { 125 | unsigned int neigh = *(p.first + i); 126 | unsigned int neigh_comm = n2c[neigh]; 127 | double neigh_w = (g.weights == NULL) ? 1. : *(p.second + i); 128 | 129 | if (neigh != node) { 130 | if (neigh_weight[neigh_comm] == -1) { 131 | neigh_weight[neigh_comm] = 0.; 132 | neigh_pos[neigh_last++] = neigh_comm; 133 | } 134 | neigh_weight[neigh_comm] += neigh_w; 135 | } 136 | } 137 | } 138 | 139 | void Community::partition2graph() { 140 | vector renumber(size, -1); 141 | for (int node = 0; node < size; node++) { 142 | renumber[n2c[node]]++; 143 | } 144 | 145 | int final = 0; 146 | for (int i = 0; i < size; i++) 147 | if (renumber[i] != -1) 148 | renumber[i] = final++; 149 | 150 | for (int i = 0; i < size; i++) { 151 | pair p = g.neighbors(i); 152 | 153 | int deg = g.nb_neighbors(i); 154 | for (int j = 0; j < deg; j++) { 155 | int neigh = *(p.first + j); 156 | cout << renumber[n2c[i]] << " " << renumber[n2c[neigh]] << endl; 157 | } 158 | } 159 | } 160 | 161 | void Community::display_partition(char* outfilename) { 162 | printf("displayin"); 163 | ofstream foutput(outfilename, ios::out | ios::app); 164 | 165 | vector renumber(size, -1); 166 | for (int node = 0; node < size; node++) { 167 | renumber[n2c[node]]++; 168 | } 169 | 170 | int final = 0; 171 | for (int i = 0; i < size; i++) 172 | if (renumber[i] != -1) 173 | renumber[i] = final++; 174 | 175 | for (int i = 0; i < size; i++) { 176 | foutput << i << " " << renumber[n2c[i]] << endl; 177 | printf("%d", i); 178 | printf(" "); 179 | printf("%d", renumber[n2c[i]]); 180 | printf("\n"); 181 | } 182 | printf("displayout"); 183 | } 184 | 185 | vector > Community::display_partition2(vector > output) { 186 | 187 | // renumber nodes 188 | // first mark communities 189 | vector renumber(size, -1); 190 | for (int node = 0; node < size; node++) { 191 | renumber[n2c[node]]++; 192 | } 193 | // then renumber communities 194 | int final = 0; 195 | for (int i = 0; i < size; i++) 196 | if (renumber[i] != -1) 197 | renumber[i] = final++; 198 | 199 | vector temp(size, 0); 200 | // assign new labels 201 | for (int i = 0; i < size; i++) { 202 | temp[i] = renumber[n2c[i]]; 203 | } 204 | // push to vector 205 | output.push_back(temp); 206 | 207 | return output; 208 | } 209 | 210 | // This function is not so nice 211 | // malloc is dirty 212 | Graph Community::partition2graph_binary() { 213 | 214 | // Renumber communities 215 | vector renumber(size, -1); 216 | for (int node = 0; node < size; node++) { 217 | renumber[n2c[node]]++; 218 | } 219 | 220 | int final = 0; 221 | for (int i = 0; i < size; i++) 222 | if (renumber[i] != -1) 223 | renumber[i] = final++; 224 | 225 | // Compute communities 226 | vector > comm_nodes(final); 227 | for (int node = 0; node < size; node++) { 228 | comm_nodes[renumber[n2c[node]]].push_back(node); 229 | } 230 | 231 | // Compute weighted graph 232 | Graph g2; 233 | g2.nb_nodes = comm_nodes.size(); 234 | g2.degrees = (unsigned long *) malloc(comm_nodes.size() * sizeof(long)); 235 | g2.links = (unsigned int *) malloc((long) g.nb_links * sizeof(int)); 236 | g2.weights = (double *) malloc((long) g.nb_links * sizeof(double)); 237 | 238 | long where = 0; 239 | int comm_deg = comm_nodes.size(); 240 | for (int comm = 0; comm < comm_deg; comm++) { 241 | map m; 242 | map::iterator it; 243 | 244 | int comm_size = comm_nodes[comm].size(); 245 | for (int node = 0; node < comm_size; node++) { 246 | pair p = g.neighbors( 247 | comm_nodes[comm][node]); 248 | int deg = g.nb_neighbors(comm_nodes[comm][node]); 249 | for (int i = 0; i < deg; i++) { 250 | int neigh = *(p.first + i); 251 | int neigh_comm = renumber[n2c[neigh]]; 252 | double neigh_weight = (g.weights == NULL) ? 1. 253 | : *(p.second + i); 254 | 255 | it = m.find(neigh_comm); 256 | if (it == m.end()) 257 | m.insert(make_pair(neigh_comm, neigh_weight)); 258 | else 259 | it->second += neigh_weight; 260 | } 261 | } 262 | 263 | g2.degrees[comm] = (comm == 0) ? m.size() : g2.degrees[comm - 1] 264 | + m.size(); 265 | g2.nb_links += m.size(); 266 | 267 | for (it = m.begin(); it != m.end(); it++) { 268 | g2.total_weight += it->second; 269 | g2.links[where] = it->first; 270 | g2.weights[where] = it->second; 271 | where++; 272 | } 273 | // cout << comm << " " << g2.weighted_degrees[comm] << endl; 274 | } 275 | 276 | g2.links = (unsigned int*) realloc(g2.links, (long) g2.nb_links 277 | * sizeof(int)); 278 | g2.weights = (double*) realloc(g2.weights, (long) g2.nb_links 279 | * sizeof(double)); 280 | 281 | return g2; 282 | } 283 | 284 | 285 | bool Community::one_level() { 286 | bool improvement = false; 287 | int nb_moves; 288 | int nb_pass_done = 0; 289 | double new_mod = modularity(); 290 | double cur_mod = new_mod; 291 | 292 | // repeat while 293 | // there is an improvement of modularity 294 | // or there is an improvement of modularity greater than a given epsilon 295 | // or a predefined number of pass have been done 296 | 297 | 298 | vector random_order(size); 299 | for (int i = 0; i < size; i++) 300 | random_order[i] = i; 301 | for (int i = 0; i < size - 1; i++) { 302 | int rand_pos = rand() % (size - i) + i; 303 | int tmp = random_order[i]; 304 | random_order[i] = random_order[rand_pos]; 305 | random_order[rand_pos] = tmp; 306 | } 307 | 308 | do { 309 | cur_mod = new_mod; 310 | nb_moves = 0; 311 | nb_pass_done++; 312 | 313 | // for each node: remove the node from its community and insert it in the best community 314 | for (int node_tmp = 0; node_tmp < size; node_tmp++) { 315 | int node = random_order[node_tmp]; 316 | int node_comm = n2c[node]; 317 | double w_degree = g.weighted_degree(node); 318 | 319 | // computation of all neighboring communities of current node 320 | neigh_comm(node); 321 | // remove node from its current community 322 | remove(node, node_comm, neigh_weight[node_comm]); 323 | 324 | // compute the nearest community for node 325 | // default choice for future insertion is the former community 326 | int best_comm = node_comm; 327 | double best_nblinks = 0.; 328 | double best_increase = 0.; 329 | for (unsigned int i = 0; i < neigh_last; i++) { 330 | double increase = modularity_gain(node, neigh_pos[i], 331 | neigh_weight[neigh_pos[i]], w_degree); 332 | if (increase > best_increase) { 333 | best_comm = neigh_pos[i]; 334 | best_nblinks = neigh_weight[neigh_pos[i]]; 335 | best_increase = increase; 336 | } 337 | } 338 | 339 | // insert node in the nearest community 340 | insert(node, best_comm, best_nblinks); 341 | 342 | if (best_comm != node_comm) 343 | nb_moves++; 344 | 345 | } 346 | 347 | new_mod = modularity(); 348 | 349 | if (nb_moves > 0) 350 | improvement = true; 351 | 352 | if (new_mod > 1) { 353 | printf( 354 | "\tThe louvain algorithm appears to be stuck in a loop. \n\tPlease first make sure that the adjacency matrix is perfectly symmetric \n\tand, if it is, try to increase the precision.\n"); 355 | for (int i = 0; i < size; i++) 356 | n2c[i] = 0; 357 | nb_pass = -10; 358 | return false; 359 | } 360 | 361 | } while (nb_moves > 0 && new_mod - cur_mod > min_modularity); 362 | 363 | return improvement; 364 | 365 | } 366 | 367 | -------------------------------------------------------------------------------- /Normalised/community.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef COMMUNITY_H 3 | #define COMMUNITY_H 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #if defined(_WIN32) || defined(WIN32) || defined(__WIN32__) || defined(_WIN64) || defined(WIN64) || defined(__WIN64__) || defined(_WINDOWS) || defined(__WINDOWS__) 14 | #define __WIN__ 15 | #endif 16 | 17 | #ifdef __WIN__ 18 | #include 19 | #endif 20 | 21 | #include "graph_binary.h" 22 | 23 | using namespace std; 24 | 25 | class Community { 26 | public: 27 | vector neigh_weight; 28 | vector neigh_pos; 29 | unsigned int neigh_last; 30 | 31 | // network to compute communities for 32 | Graph g; 33 | 34 | // nummber of nodes in the network and size of all vectors 35 | int size; 36 | 37 | // community to which each node belongs 38 | vector n2c; 39 | 40 | // used to compute the modularity participation of each community 41 | vector in, tot; 42 | 43 | // number of pass for one level computation 44 | // if -1, compute as many pass as needed to increase modularity 45 | int nb_pass; 46 | 47 | // a new pass is computed if the last one has generated an increase 48 | // greater than min_modularity 49 | // if 0. even a minor increase is enough to go for one more pass 50 | double min_modularity; 51 | 52 | // Markov Time 53 | double time; 54 | 55 | // constructors: 56 | // reads graph from file using graph constructor 57 | // type defined the weighted/unweighted status of the graph file 58 | Community(char *filename, char *filename_w, int type, int nb_pass, 59 | double min_modularity); 60 | 61 | Community(double * data, int length_data, int nbp, double minm, 62 | double timet, int type); 63 | 64 | // copy graph 65 | Community(Graph g, int nb_pass, double min_modularity, double timet); 66 | 67 | ~Community(); 68 | 69 | // display the community of each node 70 | void display(); 71 | 72 | // remove the node from its current community with which it has dnodecomm links 73 | inline void remove(int node, int comm, double dnodecomm); 74 | 75 | // insert the node in comm with which it shares dnodecomm links 76 | inline void insert(int node, int comm, double dnodecomm); 77 | 78 | // compute the gain of modularity if node where inserted in comm 79 | // given that node has dnodecomm links to comm. The formula is: 80 | // [(In(comm)+2d(node,comm))/2m - ((tot(comm)+deg(node))/2m)^2]- 81 | // [In(comm)/2m - (tot(comm)/2m)^2 - (deg(node)/2m)^2] 82 | // where In(comm) = number of half-links strictly inside comm 83 | // Tot(comm) = number of half-links inside or outside comm (sum(degrees)) 84 | // d(node,com) = number of links from node to comm 85 | // deg(node) = node degree 86 | // m = number of links 87 | inline double modularity_gain(int node, int comm, double dnodecomm, 88 | double w_degree); 89 | 90 | // compute the set of neighboring communities of node 91 | // for each community, gives the number of links from node to comm 92 | void neigh_comm(unsigned int node); 93 | 94 | // compute the modularity of the current partition 95 | double modularity(); 96 | 97 | // displays the graph of communities as computed by one_level 98 | void partition2graph(); 99 | // displays the current partition (with communities renumbered from 0 to k-1) 100 | void display_partition(char *outfilename); 101 | vector > display_partition2(vector > output); 102 | 103 | // generates the binary graph of communities as computed by one_level 104 | Graph partition2graph_binary(); 105 | 106 | // compute communities of the graph for one level 107 | // return true if some nodes have been moved 108 | bool one_level(); 109 | }; 110 | 111 | inline void Community::remove(int node, int comm, double dnodecomm) { 112 | assert(node >= 0 && node < size); 113 | 114 | tot[comm] -= g.weighted_degree(node); 115 | in[comm] -= 2 * dnodecomm + g.nb_selfloops(node); 116 | n2c[node] = -1; 117 | } 118 | 119 | inline void Community::insert(int node, int comm, double dnodecomm) { 120 | assert(node >= 0 && node < size); 121 | 122 | tot[comm] += g.weighted_degree(node); 123 | in[comm] += 2 * dnodecomm + g.nb_selfloops(node); 124 | n2c[node] = comm; 125 | } 126 | 127 | inline double Community::modularity_gain(int node, int comm, double dnodecomm, 128 | double w_degree) { 129 | assert(node >= 0 && node < size); 130 | 131 | double totc = (double) tot[comm]; 132 | double degc = (double) w_degree; 133 | double m2 = (double) g.total_weight; 134 | double dnc = (double) dnodecomm; 135 | 136 | return (time * dnc - totc * degc / m2); 137 | } 138 | 139 | #endif // COMMUNITY_H 140 | -------------------------------------------------------------------------------- /Normalised/graph_binary.cpp: -------------------------------------------------------------------------------- 1 | 2 | #if defined(macintosh) || defined(MACOS) || defined(_MACOS) || defined(__APPLE__) || defined(_MAC) || defined(MAC) || defined(mac) || defined(MACINTOSH) 3 | #define __MAC__ 4 | #endif 5 | 6 | #if defined(_WIN32) || defined(WIN32) || defined(__WIN32__) || defined(_WIN64) || defined(WIN64) || defined(__WIN64__) || defined(_WINDOWS) || defined(__WINDOWS__) 7 | #define __WIN__ 8 | #endif 9 | 10 | #if defined(linux) || defined(__linux__) || defined(_linux) || defined(LINUX) || defined(_LINUX) || defined(_UNIX) || defined(__UNIX__) || defined(__gnu_linux__) || defined(__unix__) || defined(UNIX) || defined(unix) || defined(sparc) 11 | #define __lin__ 12 | #endif 13 | 14 | #ifdef __lin__ 15 | #include 16 | #endif 17 | 18 | #ifdef __MAC__ 19 | #include 20 | #endif 21 | 22 | #include 23 | #include "graph_binary.h" 24 | #include "math.h" 25 | extern "C" { 26 | #include 27 | #include "mex.h" 28 | } 29 | 30 | Graph::Graph() { 31 | nb_nodes = 0; 32 | nb_links = 0; 33 | total_weight = 0; 34 | } 35 | 36 | Graph::Graph(char *filename, char *filename_w, int type) { 37 | ifstream finput; 38 | finput.open(filename, fstream::in | fstream::binary); 39 | 40 | // Read number of nodes on 4 bytes 41 | finput.read((char *) &nb_nodes, 4); 42 | assert(finput.rdstate() == ios::goodbit); 43 | 44 | degrees = (unsigned long *) malloc((long) nb_nodes * sizeof(long)); 45 | finput.read((char *) degrees, (long) nb_nodes * sizeof(long)); 46 | assert(finput.rdstate() == ios::goodbit); 47 | 48 | // Read links: 4 bytes for each link (each link is counted twice) 49 | nb_links = degrees[nb_nodes - 1]; 50 | 51 | 52 | 53 | links = (unsigned int *) malloc((long) nb_links * sizeof(int)); 54 | finput.read((char *) links, (long) nb_links * sizeof(int)); 55 | // assert(finput.rdstate() == ios::goodbit); 56 | assert( links); 57 | 58 | // IF WEIGHTED : read weights: 4 bytes for each link (each link is counted twice) 59 | weights = NULL; 60 | total_weight = 0; 61 | if (type == WEIGHTED) { 62 | ifstream finput_w; 63 | finput_w.open(filename_w, fstream::in | fstream::binary); 64 | 65 | weights = (double *) malloc((long) nb_links * sizeof(double)); 66 | finput_w.read((char *) weights, (long) nb_links * sizeof(double)); 67 | 68 | } 69 | 70 | // Compute total weight 71 | for (unsigned int i = 0; i < nb_nodes; i++) { 72 | total_weight += (double) weighted_degree(i); 73 | } 74 | 75 | } 76 | 77 | Graph::Graph(double * data, int length_data, int type) { 78 | 79 | //Print the integer avg of each col to matlab console 80 | nb_nodes = int(data[length_data - 1]) + 1; 81 | 82 | degrees = (unsigned long *) malloc((long) nb_nodes * sizeof(long));//new unsigned long[nb_nodes]; 83 | 84 | int tmp_node = -1; 85 | int j = 0; 86 | int tot = 0; 87 | for (int i = 0; i < length_data; i++) { 88 | tot += 1; 89 | if (int(data[i]) == tmp_node) 90 | degrees[tmp_node] += 1; 91 | else { 92 | tmp_node++; 93 | degrees[tmp_node] = tot; 94 | } 95 | } 96 | 97 | 98 | nb_links = degrees[nb_nodes - 1]; 99 | links = (unsigned int *) malloc((long) nb_links * sizeof(int));//new unsigned int[nb_links]; 100 | for (int i = 0; i < length_data; i++) { 101 | links[i] = int(data[length_data + i]); 102 | } 103 | 104 | // IF WEIGHTED : read weights: 4 bytes for each link (each link is counted twice) 105 | weights = NULL; 106 | total_weight = length_data; 107 | if (type == WEIGHTED) { 108 | weights = (double *) malloc((long) nb_links * sizeof(double));//new double[nb_links]; 109 | total_weight = 0; 110 | for (int i = 0; i < nb_links; i++) { 111 | weights[i] = (double) data[2 * length_data + i]; 112 | total_weight += (double) weights[i]; 113 | } 114 | } 115 | 116 | } 117 | 118 | void Graph::display() { 119 | for (unsigned int node = 0; node < nb_nodes; node++) { 120 | pair p = neighbors(node); 121 | for (unsigned int i = 0; i < nb_neighbors(node); i++) { 122 | if (node <= *(p.first + i)) { 123 | if (weights != NULL) 124 | cout << node << " " << *(p.first + i) << " " << *(p.second 125 | + i) << endl; 126 | else 127 | cout << node << " " << *(p.first + i) << endl; 128 | } 129 | } 130 | } 131 | } 132 | 133 | void Graph::display_reverse() { 134 | for (unsigned int node = 0; node < nb_nodes; node++) { 135 | pair p = neighbors(node); 136 | for (unsigned int i = 0; i < nb_neighbors(node); i++) { 137 | if (node > *(p.first + i)) { 138 | if (weights != NULL) 139 | cout << *(p.first + i) << " " << node << " " << *(p.second 140 | + i) << endl; 141 | else 142 | cout << *(p.first + i) << " " << node << endl; 143 | } 144 | } 145 | } 146 | } 147 | 148 | bool Graph::check_symmetry() { 149 | int error = 0; 150 | for (unsigned int node = 0; node < nb_nodes; node++) { 151 | pair p = neighbors(node); 152 | for (unsigned int i = 0; i < nb_neighbors(node); i++) { 153 | unsigned int neigh = *(p.first + i); 154 | double weight = *(p.second + i); 155 | 156 | pair p_neigh = neighbors(neigh); 157 | for (unsigned int j = 0; j < nb_neighbors(neigh); j++) { 158 | unsigned int neigh_neigh = *(p_neigh.first + j); 159 | double neigh_weight = *(p_neigh.second + j); 160 | 161 | if (node == neigh_neigh && weight != neigh_weight) { 162 | cout << node << " " << neigh << " " << weight << " " 163 | << neigh_weight << endl; 164 | if (error++ == 10) 165 | exit(0); 166 | } 167 | } 168 | } 169 | } 170 | return (error == 0); 171 | } 172 | 173 | void Graph::display_binary(char *outfile) { 174 | ofstream foutput; 175 | foutput.open(outfile, fstream::out | fstream::binary); 176 | 177 | foutput.write((char *) (&nb_nodes), 4); 178 | foutput.write((char *) (degrees), 4 * nb_nodes); 179 | foutput.write((char *) (links), 8 * nb_links); 180 | } 181 | -------------------------------------------------------------------------------- /Normalised/graph_binary.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | #if defined(macintosh) || defined(MACOS) || defined(_MACOS) || defined(__APPLE__) || defined(_MAC) || defined(MAC) || defined(mac) || defined(MACINTOSH) 4 | #define __MAC__ 5 | #endif 6 | 7 | #if defined(_WIN32) || defined(WIN32) || defined(__WIN32__) || defined(_WIN64) || defined(WIN64) || defined(__WIN64__) || defined(_WINDOWS) || defined(__WINDOWS__) 8 | #define __WIN__ 9 | #endif 10 | 11 | #if defined(linux) || defined(__linux__) || defined(_linux) || defined(LINUX) || defined(_LINUX) || defined(_UNIX) || defined(__UNIX__) || defined(__gnu_linux__) || defined(__unix__) || defined(UNIX) || defined(unix) || defined(sparc) 12 | #define __lin__ 13 | #endif 14 | 15 | #ifndef GRAPH_H 16 | #define GRAPH_H 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #ifdef __lin__ 30 | #include 31 | #endif 32 | 33 | #ifdef __WIN__ 34 | #include 35 | #endif 36 | 37 | #ifdef __MAC__ 38 | #include 39 | #endif 40 | 41 | #define WEIGHTED 0 42 | #define UNWEIGHTED 1 43 | 44 | using namespace std; 45 | 46 | class Graph { 47 | public: 48 | unsigned int nb_nodes; 49 | unsigned int nb_links; 50 | double total_weight; 51 | 52 | unsigned long *degrees; 53 | unsigned int *links; 54 | double *weights; 55 | 56 | Graph(); 57 | 58 | // binary file format is 59 | // 4 bytes for the number of nodes in the graph 60 | // 4*(nb_nodes) bytes for the cumulative degree for each node: 61 | // deg(0)=degrees[0] 62 | // deg(k)=degrees[k]-degrees[k-1] 63 | // 4*(sum_degrees) bytes for the links 64 | // IF WEIGHTED 4*(sum_degrees) bytes for the weights 65 | Graph(char *filename, char *filename_w, int type); 66 | 67 | Graph(double * data, int length_data, int type); 68 | 69 | Graph(int nb_nodes, int nb_links, double total_weight, int *degrees, 70 | int *links, double *weights); 71 | 72 | void display(void); 73 | void display_reverse(void); 74 | void display_binary(char *outfile); 75 | bool check_symmetry(); 76 | 77 | // return the number of neighbors (degree) of the node 78 | inline unsigned int nb_neighbors(unsigned int node); 79 | 80 | // return the number of self loops of the node 81 | inline double nb_selfloops(unsigned int node); 82 | 83 | // return the weighted degree of the node 84 | inline double weighted_degree(unsigned int node); 85 | 86 | // return pointers to the first neighbor and first weight of the node 87 | inline pair neighbors(unsigned int node); 88 | }; 89 | 90 | inline unsigned int Graph::nb_neighbors(unsigned int node) { 91 | assert(node>=0 && node=0 && node p = neighbors(node); 103 | for (unsigned int i = 0; i < nb_neighbors(node); i++) { 104 | if (*(p.first + i) == node) { 105 | if (weights != NULL) 106 | return (double) *(p.second + i); 107 | else 108 | return 1.; 109 | } 110 | } 111 | return 0; 112 | } 113 | 114 | inline double Graph::weighted_degree(unsigned int node) { 115 | assert(node>=0 && node p = neighbors(node); 121 | double res = 0; 122 | for (unsigned int i = 0; i < nb_neighbors(node); i++) { 123 | res += (double) *(p.second + i); 124 | } 125 | return res; 126 | } 127 | } 128 | 129 | inline pair Graph::neighbors(unsigned int node) { 130 | assert(node>=0 && node 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #ifdef __WIN__ 26 | #include 27 | #include 28 | #include 29 | #include 30 | #endif 31 | 32 | #include "graph_binary.h" 33 | #include "community.h" 34 | extern "C" { 35 | #include 36 | #include "mex.h" 37 | #include "matrix.h" 38 | 39 | } 40 | 41 | #ifdef __WIN__ 42 | int gettimeofday (struct timeval *tp, void *tz) 43 | { 44 | struct _timeb timebuffer; 45 | _ftime (&timebuffer); 46 | tp->tv_sec = timebuffer.time; 47 | tp->tv_usec = timebuffer.millitm * 1000; 48 | return 0; 49 | } 50 | #endif 51 | 52 | #ifdef __lin__ 53 | #include 54 | #include 55 | #include 56 | #endif 57 | 58 | #ifdef __MAC__ 59 | #include 60 | #include 61 | #include 62 | #endif 63 | 64 | using namespace std; 65 | 66 | double *data = NULL; 67 | int type = UNWEIGHTED; 68 | int nb_pass = 0; 69 | double precision = 0.000001; 70 | int display_level = -1; 71 | int length_data = -1; 72 | double timet = 1.0; 73 | bool hierarchy = false; 74 | 75 | void usage(char *prog_name, char *more) { 76 | cerr << more; 77 | cerr << "usage: " << prog_name << " input_file [options]" << endl << endl; 78 | cerr << "input_file: read the graph to partition from this file." << endl; 79 | cerr 80 | << "-w\t read the graph as a weighted one (weights are set to 1 otherwise)." 81 | << endl; 82 | cerr 83 | << "-q epsilon\t a given pass stops when the modularity is increased by less than epsilon." 84 | << endl; 85 | cerr 86 | << "-l k\t displays the graph of level k rather than the hierachical structure." 87 | << endl; 88 | cerr << "-h\tshow this usage message." << endl; 89 | exit(0); 90 | } 91 | 92 | bool parse_arg(int nrhs, const mxArray *prhs[]) { 93 | 94 | if (nrhs > 0) { 95 | if (mxGetN(prhs[0]) != 3 && mxGetN(prhs[0]) != 2) { 96 | printf("N=%d", mxGetN(prhs[0])); 97 | return false; 98 | } 99 | data = (double *) mxGetPr(prhs[0]); 100 | length_data = mxGetM(prhs[0]); 101 | } 102 | if (nrhs > 1) { 103 | timet = ((double) mxGetScalar(prhs[1])); 104 | } 105 | if (nrhs > 2) { 106 | precision = ((double) mxGetScalar(prhs[2])); 107 | if (precision > 1) 108 | return false; 109 | } 110 | if (nrhs > 3) { 111 | double p = (double) mxGetScalar(prhs[3]); 112 | if (p == 119) { 113 | if (mxGetN(prhs[0]) != 3) 114 | return false; 115 | type = WEIGHTED; 116 | } else if (p == 117) { 117 | type = UNWEIGHTED; 118 | } else { 119 | return false; 120 | } 121 | } 122 | if (nrhs > 4) { 123 | double p = (double) mxGetScalar(prhs[4]); 124 | if (p == 104) { 125 | hierarchy = true; 126 | } else if (p == 110) { 127 | hierarchy = false; 128 | } else { 129 | return false; 130 | } 131 | } 132 | if (nrhs > 5 || nrhs < 1) { 133 | return false; 134 | } 135 | return true; 136 | } 137 | 138 | void display_time(const char *str) { 139 | time_t rawtime; 140 | time(&rawtime); 141 | cerr << str << " : " << ctime(&rawtime); 142 | } 143 | 144 | extern "C" { 145 | void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { 146 | 147 | struct timeval tv; 148 | gettimeofday(&tv, NULL); 149 | 150 | // Initialize the random generator 151 | srand(((tv.tv_sec * 1000) + (tv.tv_usec / 1000)) * getpid()); 152 | 153 | //parse arguments 154 | if (parse_arg(nrhs, prhs) && nlhs < 4) { 155 | 156 | // Vector containing the hierarchy of the partitioning 157 | vector > output; 158 | 159 | // Creation of the Community object 160 | Community *c = new Community(data, length_data, -1, precision, timet, 161 | type); 162 | 163 | // Initial number of nodes 164 | int numberinitial = (*c).g.nb_nodes; 165 | 166 | // Calculation of the initial modularity 167 | double mod = (*c).modularity(); 168 | 169 | // First partitioning 170 | bool improvement = (*c).one_level(); 171 | 172 | // Calculation of its modularity 173 | double new_mod = (*c).modularity(); 174 | 175 | output = (*c).display_partition2(output); 176 | 177 | Graph g = (*c).partition2graph_binary(); 178 | 179 | int numberofcommunities = (*c).g.nb_nodes; 180 | int level = 0; 181 | while (improvement) { 182 | 183 | mod = new_mod; 184 | 185 | delete c; 186 | c = new Community(g, -1, precision, timet); 187 | 188 | numberofcommunities = (*c).g.nb_nodes; 189 | 190 | improvement = (*c).one_level(); 191 | if ((*c).nb_pass != -10) 192 | new_mod = (*c).modularity(); 193 | else 194 | new_mod = 0; 195 | 196 | output = (*c).display_partition2(output); 197 | 198 | g = (*c).partition2graph_binary(); 199 | level++; 200 | 201 | } 202 | 203 | numberofcommunities = (*c).g.nb_nodes; 204 | 205 | free(g.weights); 206 | free(g.links); 207 | free(g.degrees); 208 | 209 | plhs[0] = mxCreateDoubleMatrix(1, 1, mxREAL); //mxReal is our data-type 210 | 211 | //Get a pointer to the data space in our newly allocated memory 212 | double * out1 = (double*) mxGetPr(plhs[0]); 213 | 214 | out1[0] = new_mod; 215 | 216 | if (nlhs > 1) { 217 | 218 | plhs[1] = mxCreateDoubleMatrix(1, 1, mxREAL); //mxReal is our data-type 219 | 220 | //Get a pointer to the data space in our newly allocated memory 221 | double * out2 = (double*) mxGetPr(plhs[1]); 222 | 223 | out2[0] = numberofcommunities; 224 | 225 | } 226 | 227 | if (hierarchy && nlhs > 2) { 228 | int length_output = 0; 229 | for (int unsigned i = 0; i < output.size(); i++) 230 | length_output += output[i].size(); 231 | plhs[2] = mxCreateDoubleMatrix(length_output, 2, mxREAL); 232 | double * output_tab = (double*) mxGetPr(plhs[2]); 233 | double counter_temp = 0; 234 | int counter_temp2 = 0; 235 | for (int i = 0; i < output.size(); i++) { 236 | counter_temp = 0; 237 | for (int unsigned j = 0; j < output[i].size(); j++) { 238 | 239 | output_tab[counter_temp2] = (double) counter_temp; 240 | output_tab[counter_temp2 + length_output] 241 | = (double) output[i][j]; 242 | counter_temp2++; 243 | counter_temp++; 244 | } 245 | } 246 | } else if (nlhs > 2) { 247 | 248 | vector n2c(numberinitial); 249 | 250 | for (unsigned int i = 0; i < numberinitial; i++) 251 | n2c[i] = i; 252 | 253 | for (int l = 0; l < output.size(); l++) { 254 | for (unsigned int node = 0; node < numberinitial; node++) { 255 | n2c[node] = output[l][n2c[node]]; 256 | } 257 | } 258 | 259 | plhs[2] = mxCreateDoubleMatrix(numberinitial, 1, mxREAL); 260 | double * output_tab = (double*) mxGetPr(plhs[2]); 261 | for (unsigned int node = 0; node < numberinitial; node++) { 262 | output_tab[node] = (double) n2c[node]; 263 | 264 | } 265 | } 266 | 267 | delete c; 268 | 269 | } else { 270 | printf("\n\nSYNTAX:"); 271 | 272 | printf("\n\n\t[stability]=stability_louvain_LNL(graph)"); 273 | printf("\n\t[stability]=stability_louvain_LNL(graph, markov time)"); 274 | printf( 275 | "\n\t[stability]=stability_louvain_LNL(graph, markov time, precision)"); 276 | printf( 277 | "\n\t[stability]=stability_louvain_LNL(graph, markov time, precision, weighted)"); 278 | printf( 279 | "\n\t[stability]=stability_louvain_LNL(graph, markov time, precision, weighted, hierarchy)"); 280 | printf( 281 | "\n\t[stability, number of communities]=stability_louvain_LNL(graph)"); 282 | printf( 283 | "\n\t[stability, number of communities]=stability_louvain_LNL(graph, markov time)"); 284 | printf( 285 | "\n\t[stability, number of communities]=stability_louvain_LNL(graph, markov time, precision)"); 286 | printf( 287 | "\n\t[stability, number of communities]=stability_louvain_LNL(graph, markov time, precision, weighted)"); 288 | printf( 289 | "\n\t[stability, number of communities]=stability_louvain_LNL(graph, markov time, precision, weighted, hierarchy)"); 290 | printf( 291 | "\n\t[stability, number of communities, communities]=stability_louvain_LNL(graph)"); 292 | printf( 293 | "\n\t[stability, number of communities, communities]=stability_louvain_LNL(graph, markov time)"); 294 | printf( 295 | "\n\t[stability, number of communities, communities]=stability_louvain_LNL(graph, markov time, precision)"); 296 | printf( 297 | "\n\t[stability, number of communities, communities]=stability_louvain_LNL(graph, markov time, precision, weighted)"); 298 | printf( 299 | "\n\t[stability, number of communities, communities]=stability_louvain_LNL(graph, markov time, precision, weighted, hierarchy)"); 300 | 301 | printf("\n\nDESCRIPTION:"); 302 | 303 | printf("\n\n Inputs:"); 304 | printf( 305 | "\n\n\t - graph\t list of edges in the form [node, node, weight;node, node, weight;...] \n\t\t\t if the graph is weighted or [node, node;node, node;...] if the graph is unweighted."); 306 | printf( 307 | "\n\t - markov time\t time allowed to the random walkers to explore the graph \n\t\t\t (default: 1.0, corresponding to the calculation of the modularity)"); 308 | printf( 309 | "\n\t - precision\t required precision for the stability (default: 0.000001)"); 310 | printf( 311 | "\n\t - weighted\t put \'w\' if the graph is weighted or \'u\' if the graph is unweighted (default: unweighted)"); 312 | printf( 313 | "\n\t - hierarchy\t put \'h\' if the ''communities'' output should be the hierarchy of \n\t\t\t the partitions or \'n\' if it should only be the final partition (default: non-hiearchy)"); 314 | 315 | printf("\n\n Outputs:"); 316 | printf( 317 | "\n\n\t - stability\t\t\t value of the stability of the final partition."); 318 | printf( 319 | "\n\t - number of communities\t number of communities of the final partition."); 320 | printf( 321 | "\n\t - communities\t\t\t affectation of the nodes to the communities either in \n\t\t\t\t\t the final partition (if \'hierarchy\'==\'u\') or in the hierarchy of partitions (if \'hierarchy\'==\'h\')."); 322 | printf("\n\n\n"); 323 | 324 | plhs[0] = mxCreateDoubleMatrix(0, 0, mxREAL); 325 | 326 | if (nlhs > 1) 327 | plhs[1] = mxCreateDoubleMatrix(0, 0, mxREAL); 328 | 329 | if (nlhs > 2) 330 | plhs[2] = mxCreateDoubleMatrix(0, 0, mxREAL); 331 | } 332 | 333 | } 334 | } 335 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | Copyright (C) 2012 A. Delmotte, M. Schaub, S. Yaliraki, M. Barahona 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | 17 | ############################################################################### 18 | 19 | ----------------------------------------------------------------------------- 20 | Community Detection using the stability of a graph partition. 21 | ----------------------------------------------------------------------------- 22 | 23 | The code implements the stability method as introduced in the article 24 | 25 | (1) "Stability of graph communities across time scales" 26 | Delvenne, J.-C.; Yaliraki, S. N. & Barahona, M. 27 | arXiv:0812.1811 (2009) 28 | and then published in 29 | Proceedings of the National Academy of Sciences, 2010, 107, 12755-12760; 30 | 31 | and further expanded in: 32 | 33 | (2) "Laplacian Dynamics and Multiscale Modular Structure in Networks" 34 | Lambiotte, R.; Delvenne, J.-C. & Barahona, M. 35 | arxiv:0812.1770 (2009) 36 | 37 | and 38 | 39 | (3) J.-C. Delvenne, M. T. Schaub, S. N. Yaliraki, and M. Barahona, 40 | "The stability of a graph partition: A dynamics-based framework for community 41 | detection" in Time Varying Dynamical Networks (N. Ganguly, A. Mukherjee, 42 | M. Choudhury, F. Peruani, and B. Mitra, eds.), Birkhauser, Springer, 43 | 2012. to be published. 44 | 45 | 46 | To optimize the stability quality function, we use the Louvain algorithm as 47 | described in the publication: 48 | 49 | (4) "Fast unfolding of communities in large networks", 50 | Vincent D Blondel, Jean-Loup Guillaume, Renaud Lambiotte, Etienne Lefebvre, 51 | Journal of Statistical Mechanics: Theory and Experiment 2008 (10), P10008 52 | 53 | The folder /demo/ contains three examples to demonstrate the main functionality 54 | of the program: 55 | 56 | (i) demo.mat : A simple network with a multiscale community structure. 57 | (ii) Protein_Adk.mat : A graph representation of the protein Adenylate Kinase 58 | (AdK) as described in 59 | 60 | (5) "Protein multi-scale organization through graph partitioning and robustness 61 | analysis: application to the myosin–myosin light chain interaction" 62 | Delmotte, A.; Tate, E. W.; Yaliraki, S. N. & Barahona, M. 63 | Physical Biology, 2011, 8, 055010 64 | 65 | (ii) ring_of-rings.mat : The "ring-of-rings" graph introduced in: 66 | 67 | (6) "Markov dynamics as a zooming lens for multiscale community detection: 68 | non clique-like communities and the field-of-view limit" 69 | Schaub, M. T.; Delvenne, J.-C.; Yaliraki, S. N. & Barahona, M. 70 | PLoS ONE, Public Library of Science, 2012, 7, e32210 71 | 72 | Further example graphs are available on request. 73 | 74 | ***If you make use of any part of this toolbox, please cite the 75 | respective articles.*** 76 | 77 | For detailed instructions on how to compile the code in MATLAB see below. 78 | If you find a bug or have further comments, please send an email and if 79 | necessary the input file and the parameters that caused the error. 80 | 81 | NOTE: A different, somewhat more flexible backend for generalized Louvain 82 | optimization is also available from 83 | https://github.com/michaelschaub/generalizedLouvain 84 | 85 | 86 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 87 | Authors : A. Delmotte and M. Schaub 88 | Email : antoine.delmotte09@imperial.ac.uk, michael.schaub09@imperial.ac.uk 89 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 90 | 91 | ############################################################################### 92 | 93 | ----------------------------------------------------------------------------- 94 | Contributions to the code 95 | ----------------------------------------------------------------------------- 96 | 97 | The C++ code performing the stability optimization is based on the 98 | implementation of the Louvain method as available from 99 | http://sites.google.com/site/findcommunities/ 100 | (Authors: Jean-Loup Guillaume / Etienne Lefebvre) 101 | 102 | The Louvain code was adapted and extended by R. Lambiotte 103 | (http://www.lambiotte.be) to allow for the optimization of the stability quality 104 | function and was subsequently refined further by Antoine Delmotte. 105 | 106 | The MATLAB frontend was initally added by Antoine Delmotte. 107 | Final adjustments and additions, testing, and maintenance are due to 108 | Antoine Delmotte and Michael Schaub. 109 | Version 2.0 which updates the first release and includes the addition of 110 | directed stability was created by Michael Schaub (see code history for further 111 | details). 112 | 113 | ############################################################################### 114 | 115 | ----------------------------------------------------------------------------- 116 | How to install the stability package 117 | ----------------------------------------------------------------------------- 118 | 119 | 1. Open Matlab 120 | 121 | 2. Make sure you have a C++ compiler installed 122 | * For Linux, you can find one here: 123 | http://www.gnu.org/software/gcc/ 124 | * For Windows, you can use Visual C++ express: 125 | http://www.microsoft.com/express/Windows/ 126 | 127 | 3. Make sure mex is properly configured in Matlab: 128 | * Type "mex -setup" in Matlab, and choose your compiler. 129 | 130 | 4. In Matlab, go into the directory of the Stability toolbox. 131 | 132 | 5. Type "Install_Stability" in the Matlab command window. 133 | * If you get an error message concerning the libstdc++.so file, 134 | you may want to try the following manipulation: 135 | 136 | cd "Matlab_root_directory"/sys/os/glnx86/ 137 | sudo mv libgcc_s.so.1 libgcc_s.so.1.back 138 | sudo mv libstdc++.so.6 libstdc++.so.6.back 139 | 140 | 6. You will get a messge asking whether the stability toolbox should 141 | be added to your Matlab path. Answering yes will allow you to use 142 | the stability toolbox functions as standard Matlab functions. 143 | 144 | 7. Type "help stability" in Matlab to discover how to use the code. 145 | 146 | 8. Try this example to check that everything is working: 147 | 148 | cd('demo'); % go into the demo directory (in the stability folder) 149 | load demo; % load data and then run stability 150 | [S, N, VI, C] = stability(Graph,Time,'plot','v'); 151 | 152 | NOTES: 153 | 154 | * The install script provides the option to add the bin folder to your 155 | Matlab path. This will enable you to use stability as a standard Matlab 156 | function from any directory. If you don't want this option any more, 157 | just remove it from the path by going in File/Set Path. 158 | 159 | * If you get a warning message concerning savepath, and you want the 160 | stability code to be in your path, go, after the installation, in 161 | File/Set Path, and choose "save". Then choose where you want pathdef.m 162 | to be saved. If at the next matlab startup, you notice that stability is 163 | not in your matlab path anymore, try editing/creating the "startup.m" file 164 | from your matlab user folder (type userpath to know where it is located) 165 | and add the following line: addpath(' path to bin folder of stability 166 | package '). Alternatively, if you are the only user on your machine, you 167 | can start matlab as a superuser ("sudo matlab" in linux) and rerun the 168 | "Install_Stability" script. This will permanently add the stability folder 169 | in the path for all users. 170 | 171 | * To speed up the calculations, you might consider adding the 172 | option 'noVI'. This disables the calculation of the variation of information, 173 | which is usually slow at small Markov times, when the number of 174 | communities found is big. 175 | Another option is to decrease the number of optimisations on which the variation 176 | of information is calculated. To do so, add the option 'M' and put a value 177 | such that M < L (L is the number of louvain optimisations). 178 | Example: 179 | 180 | [S, N, VI, C] = stability(Graph,time,'plot','v', 'L', 100, 'M', 10); 181 | 182 | -------------------------------------------------------------------------------- /bin/compare_partitions_over_time.m: -------------------------------------------------------------------------------- 1 | function [ vi_mat ] = compare_partitions_over_time( C1, C2, T) 2 | %COMPARE_PARTITIONS_OVER_TIME Compute a VI(t,t') matrix for two given 3 | %partition sequences and a Time vector 4 | 5 | nr_steps = length(T); 6 | vi_mat = zeros(nr_steps); 7 | 8 | for i = 1:nr_steps 9 | for j= i:nr_steps 10 | vi_mat(i,j) = varinfo([C1(:,i),C2(:,j)]'); 11 | end 12 | end 13 | 14 | vi_mat = vi_mat+vi_mat' - diag(diag(vi_mat)); 15 | 16 | surf(T,T,vi_mat); 17 | xlabel('Time t_1'); 18 | ylabel('Time t_2'); 19 | zlabel('Variation of information'); 20 | 21 | end 22 | 23 | -------------------------------------------------------------------------------- /bin/stability.m: -------------------------------------------------------------------------------- 1 | function [S, N, VI, C] = stability(G, T, varargin) 2 | %STABILITY Graph partitioning optimizing stability with the Louvain 3 | % algorithm 4 | % 5 | % [S, N, VI, C] = STABILITY(G, T) finds the optimal partitions of the 6 | % graph G by optimizing the stability at each Markov time in vector T. G 7 | % can either be the list of the edges in the graph (in the form [node i, 8 | % node j, weight of link i-j; node k, node l, weight of link k-l;...] if 9 | % the graph is weighted, or [node i, node j; node k, node l;...] if the 10 | % graph is unweighted) or the adjacendy matrix of the graph. S, N, VI and 11 | % C contain respectively the stability, the number of communities, the 12 | % variation of information, and the optimal partition for each 13 | % Markov time contained in T. If T is not specified, the modularity 14 | % (equivalent to stability for T=1) is calculated. Ideally, Markov time 15 | % should be sampled exponentially (e.g.: T = 10.^[-2:0.01:2]). 16 | % 17 | % [S, N, VI, C] = STABILITY(G, T,'PARAM',VALUE) accepts one or more 18 | % comma-separated parameter name/value pairs. For a list of parameters 19 | % and values, see "Parameter Options." 20 | % 21 | % 22 | % Parameter Options: 23 | % 24 | % Parameter Value Default 25 | % --------- ----- ------- 26 | % L Number of optimisations of the 100 27 | % Louvain algorithm to be done at 28 | % each Markov time. 29 | % 30 | % M The top M partitions among the L 100 31 | % given at each Markov time by the 32 | % L louvain optimisations will be 33 | % used to compute the variation of 34 | % information. 35 | % 36 | % laplacian Allows to choose which type of 'normalised' 37 | % laplacian should be used to 38 | % calculate the stability. It can 39 | % either be 'combinatorial', or 40 | % 'normalised'. 41 | % 42 | % directed activate stability for directed none 43 | % graphs. Note that transition matrices 44 | % are defined for left multiplications 45 | % here, i.e. A_ij is the link from i to j. 46 | % 47 | % teleport_tau teleportation probability 0.15 48 | % (only active if directed == true) 49 | % 50 | % 51 | % noVI Disables the calculation of the none 52 | % robustness of the partitions. 53 | % Disabling this can significantly 54 | % speed up the calculations. 55 | % 56 | % out Enables saving step by step the '' 57 | % partitions found in a .mat file 58 | % located in the current folder, 59 | % along with the number of 60 | % communities (N), the value of 61 | % Stability (S), and the variation 62 | % of information (VI) for the 63 | % partitions obtained at each 64 | % Markov Time. 65 | % 66 | % full Enforces the calculation of the none 67 | % full stability. 68 | % 69 | % linearised Enforces the calculation of the none 70 | % linearised stability. 71 | % 72 | % nocheck Disables the checks for the none 73 | % encoding of the graph. This can 74 | % save computational time but can 75 | % also lead to serious errors if 76 | % the graph has not been properly 77 | % encoded. 78 | % 79 | % prec Precision: defines a threshold for 1e-9 80 | % the range of weights allowed in 81 | % the laplacian exponential matrix 82 | % of the full stability. 83 | % 84 | % plot Plots the plots the results of none 85 | % the stability, number of 86 | % communities and variation of 87 | % information as a function of the 88 | % Markov time. 89 | % 90 | % v Verbose mode. none 91 | % 92 | % p Parallel mode. none 93 | % 94 | % t Output as text files: none 95 | % Enables saving step by step the 96 | % partitions found at each Markov 97 | % time in a text file located in a 98 | % folder named 'Partitions', as 99 | % well as the outputs in a text 100 | % file output_'...'.stdout matlab 101 | % The option 'out' must be on. 102 | % 103 | % 104 | 105 | 106 | 107 | % Unparsed default parameters 108 | Graph = []; % List of edges of the graph to be partitioned 109 | Time = 1; % Markov times at which the graph should be partitioned 110 | flag_matlabpool = false; 111 | 112 | 113 | %$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$% 114 | %$ $% 115 | %$ Arguments parsing $% 116 | %$ $% 117 | %$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$% 118 | 119 | 120 | 121 | [StabilityFunction, OutputFile, prefix, Full, Sanity, plotStability, verbose, TextOutput, PARAMS] = parseinput(length(varargin),G,varargin); 122 | 123 | 124 | 125 | % Argument 1: G 126 | 127 | if nargin > 0 128 | if size(G,1) == size(G,2) && ~issparse(G) 129 | G=sparse(G); 130 | end 131 | % Check if the graph is correctly encoded 132 | if Sanity 133 | G=check(G, verbose, PARAMS); 134 | end 135 | % If the full stability is to be computed, Graph should be the 136 | % adjacency matrix. 137 | if Full 138 | if size(G,1) ~= size(G,2) 139 | if size(G,2)==3 140 | Graph=sparse(G(:,1)+1,G(:,2)+1,G(:,3)); 141 | elseif size(G,2)==2 142 | Graph=sparse(G(:,1)+1,G(:,2)+1,ones(length(G(:,1)),1)); 143 | else 144 | error('Wrong size for G: G should be a graph saved either as a list of edges (size(G)=[N,3] if weighted, size(G)=[N,2] if unweighted) or as an adjacency matrix (size(G)=[N,N])'); 145 | end 146 | else 147 | Graph = sparse(G); 148 | end 149 | PARAMS.NbNodes = size(Graph,2); 150 | % if the linearised stability is to be computed, Graph should be the 151 | % list of edges. 152 | else 153 | if size(G,1) == size(G,2) 154 | [rows,cols,vals] = find(G'); 155 | if sum(vals)==length(vals) 156 | Graph=[cols-1, rows-1 ones(size(cols))]; 157 | else 158 | Graph=[cols-1, rows-1, vals]; 159 | end 160 | clear rows cols vals; 161 | elseif size(G,2)==3 162 | Graph=G; 163 | else 164 | error('Wrong size for G: G should be a graph saved either as a list of edges (size(G)=[N,3] if weighted, size(G)=[N,2] if unweighted) or as an adjacency matrix (size(G)=[N,N])'); 165 | end 166 | PARAMS.NbNodes = max(Graph(:,1))+1; 167 | end 168 | else 169 | error('Please provide at least the graph to be partitioned. Type "help stability" for more information.'); 170 | end 171 | 172 | % Argument 2: T 173 | 174 | if nargin > 1 175 | if (isvector(T) && isnumeric(T)) 176 | Time=T; 177 | else 178 | error('The second argument should be a numerical vector. Type "help stability" for more information.'); 179 | end 180 | end 181 | 182 | % Parallel computation: Initialize the number of cores if matlabpool is not 183 | % yet running. 184 | if PARAMS.ComputeParallel && (matlabpool('size') == 0) 185 | flag_matlabpool = true; 186 | matlabpool 187 | end 188 | 189 | %$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$% 190 | %$ $% 191 | %$ Computation of the stability $% 192 | %$ $% 193 | %$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$% 194 | 195 | if verbose 196 | c = clock; 197 | disp(' '); 198 | disp([' Stability will be computed with the following parameters:']); 199 | disp(' '); 200 | if Full; disp(' - Full Stability'); else disp(' - Linearised Stability'); end 201 | if strfind(func2str(StabilityFunction), 'N'); disp([' - Normalised laplacian']); else disp([' - Combinatorial laplacian']); end 202 | if PARAMS.directed; disp([' - DIRECTED graph; teleportation parameter set to ' num2str(PARAMS.teleport_tau)]); end 203 | if PARAMS.ComputeVI; disp([' - Computation of the variation of information (VI): Yes']); else disp([' - Computation of the variation of information: No']); end 204 | if OutputFile; disp([' - Save the results in a file: Yes ' prefix]); else disp([' - Save the results in a file: No']); end 205 | if OutputFile; disp([' - Saved file prefix: ' prefix]); end 206 | if Sanity; disp([' - Check the input graph: Yes']); else disp([' - Check the input graph: No']); end 207 | if plotStability; disp([' - Plot the results: Yes']); else disp([' - Plot the results: No']); end 208 | if verbose; disp([' - Verbose mode: Yes']); else disp([' - Verbose mode: No']); end 209 | if PARAMS.ComputeParallel; disp([' - Parallel computation: Yes']); else disp([' - Parallel computation: No']); end 210 | disp([' - Number of Louvain iterations: ' int2str(PARAMS.NbLouvain)]); 211 | disp([' - Number of Louvain iterations used for the computation of VI: ' int2str(PARAMS.M)]); 212 | disp([' - Precision used: ' num2str(PARAMS.Precision)]); 213 | disp(' '); 214 | tstart=tic; 215 | end 216 | 217 | 218 | % Initialisation 219 | S = zeros(1, length(Time)); 220 | N = zeros(1, length(Time)); 221 | VI = zeros(1, length(Time)); 222 | C = zeros(PARAMS.NbNodes, length(Time)); 223 | 224 | if TextOutput 225 | mkdir(['Partitions_' prefix]); 226 | end 227 | 228 | if OutputFile 229 | save(['Stability_' prefix '.mat'],'Time','S','N','VI','C'); 230 | end 231 | 232 | if plotStability 233 | figure_handle = figure; 234 | end 235 | 236 | if verbose 237 | step_prec=0; 238 | end 239 | 240 | 241 | % Loop over all Markov times 242 | for t=1:length(Time) 243 | 244 | if verbose 245 | disp([' Partitioning for Markov time = ' num2str(Time(t),'%10.6f') '...']); 246 | end 247 | 248 | 249 | 250 | [S(t), N(t), C(:,t), VI(t) VAROUT] = StabilityFunction(Graph, Time(t), PARAMS); 251 | if isfield(VAROUT,'precomputed') 252 | PARAMS.precomputed = VAROUT.precomputed; 253 | PARAMS.pi = VAROUT.pi; 254 | PARAMS.P = VAROUT.P; 255 | end 256 | 257 | if plotStability && t>1 258 | stability_plot(Time,t,S,N,VI,PARAMS.ComputeVI,figure_handle); 259 | end 260 | 261 | if TextOutput 262 | cd(['Partitions_' prefix]); 263 | dlmwrite(['Partition_' prefix '_' num2str(Time(t),'%10.6f') '.dat'],[[1:PARAMS.NbNodes]',C(:,t)],'delimiter','\t'); 264 | cd ..; 265 | dlmwrite(['Stability_' prefix '.stdout'],[Time(t), S(t), N(t), VI(t)],'-append', 'delimiter','\t') 266 | end 267 | 268 | if OutputFile 269 | save(['Stability_' prefix '.mat'],'Time','S','N','VI','C','-append'); 270 | end 271 | 272 | 273 | if verbose && 100*t/length(Time) >= step_prec+10 274 | disp(' '); 275 | disp([' Completed: ' num2str(round(100*t/length(Time)),10) '%']); 276 | remaining_time=toc(tstart)*(1-t/length(Time))/(t/length(Time)); 277 | nb_hours = floor(remaining_time/3600); 278 | nb_min = floor((remaining_time - nb_hours*3600)/60); 279 | nb_sec = round(remaining_time - nb_hours*3600 - nb_min*60); 280 | disp([' Estimated time remaining: ' datestr([2011 1 1 nb_hours nb_min nb_sec], 'HH:MM:SS')]);%num2str(nb_hours) ':' num2str(nb_min) ':' num2str(nb_sec)]); 281 | disp(' '); 282 | step_prec = floor(100*t/length(Time)); 283 | end 284 | 285 | end 286 | 287 | if verbose 288 | c = clock; 289 | disp(' '); 290 | disp([' Partitioning of the graph finished at ' datestr([2011 1 1 c(4) c(5) c(6)], 'HH:MM:SS')]); 291 | remaining_time=toc(tstart); 292 | nb_hours = floor(remaining_time/3600); 293 | nb_min = floor((remaining_time - nb_hours*3600)/60); 294 | nb_sec = round(remaining_time - nb_hours*3600 - nb_min*60); 295 | disp([' Total time needed: ' datestr([2011 1 1 nb_hours nb_min nb_sec], 'HH:MM:SS')]);%num2str(nb_hours) ':' num2str(nb_min) ':' num2str(nb_sec)]); 296 | end 297 | 298 | 299 | if OutputFile 300 | save(['Stability_' prefix '.mat'],'Time','S','N','VI','C','-append'); 301 | end 302 | 303 | if flag_matlabpool 304 | matlabpool close; 305 | end 306 | 307 | 308 | 309 | end 310 | 311 | %------------------------------------------------------------------------------ 312 | function [StabilityFunction, OutputFile, prefix, Full, Sanity, plotStability, verbose, TextOutput,PARAMS] = parseinput(options,G,varargin) 313 | % Parse the options from the command line 314 | 315 | %$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$% 316 | %$ $% 317 | %$ Default parameters $% 318 | %$ $% 319 | %$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$% 320 | 321 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 322 | % "Global" options relevant for output and control flow 323 | 324 | stability_type_specified=false; 325 | threshold_nnodes_full = 1000; 326 | threshold_nedges_full = 5000; 327 | 328 | OutputFile = false; % No output file by default. 329 | 330 | Laplacian = 'Normalised'; % Default Laplacian 331 | Full = false; % If true, performs the full stability 332 | Sanity = true; % If true, performs the graph sanity checks 333 | plotStability = false; % If true, plots the results of the stability, number of communities and variation of information vs Markov time. 334 | verbose = false; % Toggle verbose mode 335 | prefix = ''; % Output prefix 336 | TextOutput = false; % Toggles the text output 337 | 338 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 339 | % Options stored in struct relevant for optimization etc. 340 | PARAMS = struct; % create empty structure for storing parameters 341 | PARAMS.precomputed = false; % Flag for precomputed transition matrix + stationary distribution 342 | PARAMS.directed = false; % enables dealing with directed graphs 343 | PARAMS.ComputeVI = true; % True if the variation of information should be computed 344 | PARAMS.ComputeES = false; % True if edge statistics should be computed 345 | PARAMS.ComputeParallel = false; % Toggles the computation in parallel 346 | PARAMS.NbLouvain = 100; % Number of louvain optimisations at each Markov time 347 | PARAMS.NbNodes = 0; % Total number of nodes; 348 | PARAMS.Precision = 1e-9; % Threshold for stability and edges weigths 349 | PARAMS.M = 100; % Top M partitions among the L found by louvain are used to compute the variation of information 350 | PARAMS.K = NaN; % K stabilities value, only relevant for Ruelle random walk and k stabilities. K =-1 corresponds to the normalised Laplacian 351 | PARAMS.teleport_tau = 0.15; % teleportation probability (only relevant for directed graphs) 352 | 353 | % actual parsing begins here 354 | attributes={'novi', 'l', 'm', 'out', 'full','linearised', 'nocheck', 'laplacian', 'prec', 'plot','v','t','p','k','directed','teleport','precomputed'}; 355 | 356 | if options > 0 357 | 358 | varargin = varargin{:}; % extract cell array input from varargin 359 | 360 | % test whether attribute-value pairs are specified, or fixed parameter order 361 | stringoptions = lower(varargin(cellfun('isclass',varargin,'char'))); 362 | attributeindexesinoptionlist = ismember(stringoptions,attributes); 363 | newinputform = any(attributeindexesinoptionlist); 364 | if newinputform 365 | % parse values to functions parameters 366 | i = 1; 367 | while (i <= length(varargin)) 368 | if strcmpi(varargin{i},'full') 369 | stability_type_specified=true; 370 | Full = true; 371 | i = i+1; 372 | elseif strcmpi(varargin{i},'linearised') 373 | if stability_type_specified 374 | warning('The program can run either the linearised or the full stability, not both simultaneously. Please choose only one of them. Linearised stability will be used here.'); 375 | end 376 | stability_type_specified=true; 377 | Full = false; 378 | i = i+1; 379 | elseif strcmpi(varargin{i},'directed') 380 | PARAMS.directed = true; 381 | i = i+1; 382 | elseif strcmpi(varargin{i},'novi') 383 | PARAMS.ComputeVI = false; 384 | i = i+1; 385 | elseif strcmpi(varargin{i},'nocheck') 386 | Sanity = false; 387 | i = i+1; 388 | elseif strcmpi(varargin{i},'plot') 389 | plotStability = true; 390 | i = i+1; 391 | elseif strcmpi(varargin{i},'v') 392 | verbose = true; 393 | i = i+1; 394 | elseif strcmpi(varargin{i},'precomputed') 395 | PARAMS.precomputed = true; 396 | i = i+1; 397 | elseif strcmpi(varargin{i},'p') 398 | if exist('matlabpool','file') 399 | PARAMS.ComputeParallel = true; 400 | else 401 | PARAMS.ComputeParallel = false; 402 | warning('The Parallel Computing Toolbox of Matlab does not appear to be installed. Defaulting to single node computation...'); 403 | end 404 | i = i+1; 405 | elseif strcmpi(varargin{i},'t') 406 | TextOutput = true; 407 | i = i+1; 408 | else 409 | %Check to make sure that there is a pair to go with 410 | %this argument. 411 | if length(varargin) < i + 1 412 | error('MATLAB:stability:AttributeList', ... 413 | 'Attribute %s requires a matching value', varargin{i}); 414 | elseif strcmpi(varargin{i},'laplacian') 415 | if ischar(varargin{i+1}) 416 | Laplacian = varargin{i+1}; 417 | else 418 | error('MATLAB:stability:laplacian',... 419 | 'Please provide a matching value for attribute laplacian. It must either be ''normalised'' or ''combinatorial''.'); 420 | end 421 | elseif strcmpi(varargin{i},'l') 422 | if isnumeric(varargin{i+1}) 423 | PARAMS.NbLouvain = round(varargin{i+1}); 424 | PARAMS.M = round(varargin{i+1}); 425 | end 426 | elseif strcmpi(varargin{i},'prec') 427 | if isnumeric(varargin{i+1}) 428 | PARAMS.Precision = varargin{i+1}; 429 | end 430 | elseif strcmpi(varargin{i},'m') 431 | if isnumeric(varargin{i+1}) 432 | PARAMS.M = varargin{i+1}; 433 | end 434 | elseif strcmpi(varargin{i},'k') 435 | if isnumeric(varargin{i+1}) 436 | PARAMS.K = varargin{i+1}; 437 | end 438 | elseif strcmpi(varargin{i},'teleport') 439 | if isnumeric(varargin{i+1}) 440 | PARAMS.teleport_tau = varargin{i+1}; 441 | end 442 | elseif strcmpi(varargin{i},'out') 443 | if ischar(varargin{i+1}) 444 | OutputFile = true; 445 | prefix = varargin{i+1}; 446 | else 447 | error('MATLAB:stability:out',... 448 | 'Please provide a matching value for attribute out. It must be a string.'); 449 | end 450 | else 451 | error('MATLAB:stability:Attribute',... 452 | 'Invalid attribute tag: %s', varargin{i}); 453 | end 454 | i = i+2; 455 | end 456 | end 457 | else 458 | if ischar(varargin{1}) 459 | error('MATLAB:stability:Attribute',... 460 | 'Invalid attribute tag: %s', varargin{1}); 461 | else 462 | error('MATLAB:stability:Attribute',... 463 | 'Invalid attribute tag: %d', varargin{1}); 464 | end 465 | end 466 | end 467 | 468 | TextOutput = TextOutput & OutputFile; 469 | 470 | if ~stability_type_specified 471 | if (size(G,1) == size(G,2) && nnz(G)> from i to j 533 | if PARAMS.directed == true 534 | dout = sum(Graph,2); 535 | dangling = (dout==0); 536 | dout(dangling) = 1; 537 | Dout = sparse(diag(dout)); 538 | clear dout; 539 | M = (1-PARAMS.teleport_tau)*(Dout\Graph); % deterministic part of transition 540 | % teleportation according to arXiv:0812.1770 541 | M = M + diag(PARAMS.teleport_tau + dangling.*(1-PARAMS.teleport_tau))... 542 | * ones(PARAMS.NbNodes)/PARAMS.NbNodes; 543 | 544 | clear Dout dangling 545 | [v lambda_all] = eigs(M'); % largest eigenvalue of transition matrix corresponds to stat.distribution. 546 | lambda = max(diag(lambda_all)); 547 | v = v(:,diag(lambda_all) == lambda); 548 | v = abs(v); % make sure eigenvector is positive 549 | clear lambda; 550 | % store results for future use 551 | VAROUT.precomputed = true; 552 | VAROUT.pi = v/sum(v); 553 | VAROUT.P = M; 554 | % now compute exponential transition matrix 555 | solution = diag(v/sum(v))*expm(time* (M-eye(size(M))) ); 556 | clear M v; 557 | % symmetrize solution 558 | solution = (solution+solution')/2; 559 | 560 | 561 | % undirected case 562 | else 563 | % Generate the matrix exponential 564 | trans=sparse(diag( (sum(Graph)).^(-1) ) * Graph); %(stochastic) transition matrix 565 | Lap=sparse(trans-eye(PARAMS.NbNodes)); 566 | % store results for future use 567 | VAROUT.precomputed = true; 568 | VAROUT.P = trans; 569 | 570 | clear trans; 571 | exponential=expm(time.*Lap); 572 | clear Lap; 573 | 574 | PI=sparse((diag(sum(Graph)))/sum(sum(Graph))); %diag matrix with stat distr 575 | VAROUT.pi = diag(PI); % store results for future use 576 | 577 | solution=sparse(PI*exponential); 578 | clear exponential; 579 | clear PI; 580 | 581 | 582 | end 583 | 584 | % stationary distribution and "transition matrix" have been computed before 585 | else 586 | if PARAMS.directed == true 587 | solution = diag(PARAMS.pi)*expm(time* (PARAMS.P - eye(size(PARAMS.P))) ); 588 | solution = (solution +solution')/2; % symetrization needed for directed case 589 | else 590 | solution = diag(PARAMS.pi)*expm(time* (PARAMS.P - eye(size(PARAMS.P))) ); 591 | end 592 | end 593 | 594 | 595 | 596 | % prune out weights that are too small as defined by precision 597 | solution=max(max(solution))*PARAMS.Precision*round(solution/(max(max(solution))*PARAMS.Precision)); 598 | [row,col,val] = find(solution); 599 | clear solution 600 | graph=[col-1,row-1,val]; 601 | weighted = 'w'; 602 | 603 | % Optimize louvain NbLouvain times 604 | lnk = zeros(PARAMS.NbNodes, PARAMS.NbLouvain); 605 | lnkS = zeros(PARAMS.NbLouvain,1); 606 | precision = PARAMS.Precision; 607 | if PARAMS.ComputeParallel 608 | parfor l=1:PARAMS.NbLouvain 609 | [stability, nb_comm, communities] = stability_louvain_LNL(graph, 1, precision, weighted); 610 | lnk(:,l) = communities; 611 | lnkS(l) = stability; 612 | end 613 | else 614 | for l=1:PARAMS.NbLouvain 615 | [stability, nb_comm, communities] = stability_louvain_LNL(graph, 1, precision, weighted); 616 | lnk(:,l) = communities; 617 | lnkS(l) = stability; 618 | end 619 | end 620 | [S,indexS]=max(lnkS); 621 | C=lnk(:,indexS); 622 | N=max(C)+1; 623 | 624 | clear communities; 625 | clear graph; 626 | 627 | ComputeVI = PARAMS.ComputeVI; 628 | NbLouvain = PARAMS.NbLouvain; 629 | NbNodes = PARAMS.NbNodes; 630 | M = PARAMS.M ; 631 | 632 | if ComputeVI && nnz(max(lnk)==NbNodes-1)~=NbLouvain && nnz(max(lnk)==0)~=NbLouvain 633 | VI = computeRobustness(lnk, lnkS, M, PARAMS.ComputeParallel); 634 | else 635 | VI = 0; 636 | end 637 | 638 | clear lnk; 639 | 640 | end 641 | 642 | %------------------------------------------------------------------------------ 643 | function [S, N, C, VI, VAROUT] = louvain_FCL(Graph, time, PARAMS) 644 | % Computes the full combinatorial stability 645 | 646 | VAROUT =[]; % init varying outputs 647 | 648 | ComputeVI = PARAMS.ComputeVI; 649 | precision = PARAMS.Precision; 650 | NbLouvain = PARAMS.NbLouvain; 651 | 652 | 653 | 654 | % "transition matrix" and pi unknown 655 | if PARAMS.precomputed == false 656 | % directed case: M_ij >> from i to j 657 | if PARAMS.directed == true 658 | % Directed part so far not implemented, different variants are possible 659 | error(['This parameter combination is not allowed! If you want to use directed stability, use the normalised Laplacian']); 660 | % undirected case 661 | else 662 | % standard Laplacian and average degree 663 | av_degree = sum(sum(Graph))/PARAMS.NbNodes; 664 | Lap= -(Graph-diag(sum(Graph))); 665 | % store results for future use 666 | VAROUT.precomputed = true; 667 | VAROUT.P = Lap/av_degree; 668 | 669 | 670 | % Generate the matrix exponential 671 | exponential=sparse(expm(-time.*Lap/av_degree)); 672 | clear Lap; 673 | 674 | PI=sparse(eye(PARAMS.NbNodes)/PARAMS.NbNodes); %diag matrix with stat distr 675 | VAROUT.pi = diag(PI); % store results for future use 676 | 677 | solution=sparse(PI*exponential); 678 | clear exponential; 679 | clear PI; 680 | 681 | 682 | end 683 | 684 | % stationary distribution and "transition matrix" have been computed before 685 | else 686 | if PARAMS.directed == true 687 | solution = diag(PARAMS.pi)*expm(-time* PARAMS.P); 688 | solution = (solution +solution')/2; % symetrization needed for directed case 689 | else 690 | solution = diag(PARAMS.pi)*expm(-time* PARAMS.P); 691 | end 692 | end 693 | 694 | 695 | 696 | % prune out weights that are too small as defined by precision 697 | solution=max(max(solution))*PARAMS.Precision*round(solution/(max(max(solution))*PARAMS.Precision)); 698 | [row,col,val] = find(solution); 699 | clear solution 700 | graph=[col-1,row-1,val]; 701 | weighted='w'; 702 | 703 | % Optimize louvain NbLouvain times 704 | lnk = zeros(PARAMS.NbNodes, PARAMS.NbLouvain); 705 | lnkS = zeros(PARAMS.NbLouvain,1); 706 | precision = PARAMS.Precision; 707 | if PARAMS.ComputeParallel 708 | parfor l=1:PARAMS.NbLouvain 709 | [stability, nb_comm, communities] = stability_louvain_LNL(graph, 1, precision, weighted); 710 | lnk(:,l) = communities; 711 | lnkS(l) = stability; 712 | end 713 | else 714 | for l=1:PARAMS.NbLouvain 715 | [stability, nb_comm, communities] = stability_louvain_LNL(graph, 1, precision, weighted); 716 | lnk(:,l) = communities; 717 | lnkS(l) = stability; 718 | end 719 | end 720 | [S,indexS]=max(lnkS); 721 | C=lnk(:,indexS); 722 | N=max(C)+1; 723 | 724 | clear communities; 725 | clear graph; 726 | 727 | ComputeVI = PARAMS.ComputeVI; 728 | NbLouvain = PARAMS.NbLouvain; 729 | NbNodes = PARAMS.NbNodes; 730 | M = PARAMS.M ; 731 | 732 | if ComputeVI && nnz(max(lnk)==NbNodes-1)~=NbLouvain && nnz(max(lnk)==0)~=NbLouvain 733 | VI = computeRobustness(lnk, lnkS, M, PARAMS.ComputeParallel); 734 | else 735 | VI = 0; 736 | end 737 | 738 | clear lnk; 739 | 740 | end 741 | 742 | %------------------------------------------------------------------------------ 743 | function [S, N, C, VI, VAROUT] = louvain_LCL(Graph, time, PARAMS) 744 | VAROUT =[]; % init varying outputs 745 | 746 | if PARAMS.directed == true 747 | % Directed part so far not implemented, different variants are possible 748 | error(['This parameter combination is not allowed! If you want to use directed stability, use the normalised Laplacian']); 749 | end 750 | 751 | 752 | ComputeVI = PARAMS.ComputeVI ; 753 | precision = PARAMS.Precision; 754 | NbLouvain = PARAMS.NbLouvain; 755 | M = PARAMS.M ; 756 | NbNodes = PARAMS.NbNodes; 757 | ComputeParallel = PARAMS.ComputeParallel; 758 | weighted = 'w'; 759 | 760 | % Optimize louvain NbLouvain times 761 | lnk = zeros(NbNodes, NbLouvain); 762 | lnkS = zeros(NbLouvain,1); 763 | if ComputeParallel 764 | parfor l=1:NbLouvain 765 | [stability, nb_comm, communities] = stability_louvain_LCL(Graph, time, precision, weighted); 766 | lnk(:,l) = communities; 767 | lnkS(l) = stability; 768 | end 769 | else 770 | for l=1:NbLouvain 771 | [stability, nb_comm, communities] = stability_louvain_LCL(Graph, time, precision, weighted); 772 | lnk(:,l) = communities; 773 | lnkS(l) = stability; 774 | end 775 | end 776 | [S,indexS]=max(lnkS); 777 | C=lnk(:,indexS); 778 | N=max(C)+1; 779 | 780 | clear communities; 781 | clear Graph; 782 | 783 | if ComputeVI && nnz(max(lnk)==NbNodes-1)~=NbLouvain && nnz(max(lnk)==0)~=NbLouvain 784 | VI = computeRobustness(lnk, lnkS, M,ComputeParallel); 785 | else 786 | VI = 0; 787 | end 788 | 789 | clear lnk; 790 | 791 | end 792 | %------------------------------------------------------------------------------ 793 | function [S, N, C, VI, VAROUT] = louvain_LNL(Graph, time, PARAMS) 794 | VAROUT =[]; % init varying outputs 795 | 796 | weighted = 'w'; 797 | 798 | 799 | % directed case: M_ij >> from i to j 800 | if PARAMS.directed == true 801 | Graph = sparse(Graph(:,1)+1,Graph(:,2)+1,Graph(:,3),PARAMS.NbNodes,PARAMS.NbNodes); 802 | % "transition matrix" and pi unknown 803 | if PARAMS.precomputed == false 804 | dout = sum(Graph,2); 805 | dangling = (dout==0); 806 | dout(dangling) = 1; 807 | Dout = sparse(diag(dout)); 808 | clear dout; 809 | M = (1-PARAMS.teleport_tau)*(Dout\Graph); % deterministic part of transition 810 | % teleportation according to arXiv:0812.1770 811 | M = M + diag(PARAMS.teleport_tau + dangling.*(1-PARAMS.teleport_tau))... 812 | * ones(PARAMS.NbNodes)/PARAMS.NbNodes; 813 | 814 | clear Dout dangling 815 | [v lambda_all] = eigs(M'); % largest eigenvalue of transition matrix corresponds to stat.distribution. 816 | lambda = max(diag(lambda_all)); 817 | v = v(:,diag(lambda_all) == lambda); 818 | v = abs(v); % make sure eigenvector is positive 819 | clear lambda; 820 | % store results for future use 821 | VAROUT.precomputed = true; 822 | VAROUT.pi = v/sum(v); 823 | VAROUT.P = M; 824 | %symmetrise due to trace optimsation 825 | solution = diag(VAROUT.pi)*M; 826 | solution = (solution +solution')/2; 827 | else 828 | solution = diag(PARAMS.pi)* PARAMS.P; 829 | solution = (solution +solution')/2; 830 | end 831 | [row,col,val] = find(solution); 832 | clear solution 833 | Graph = [col-1,row-1,val]; 834 | end 835 | 836 | 837 | 838 | % Optimize louvain NbLouvain times 839 | lnk = zeros(PARAMS.NbNodes, PARAMS.NbLouvain); 840 | lnkS = zeros(PARAMS.NbLouvain,1); 841 | if PARAMS.ComputeParallel 842 | parfor l=1:PARAMS.NbLouvain 843 | [stability, ~, communities] = stability_louvain_LNL(Graph, time, PARAMS.Precision, weighted); 844 | lnk(:,l) = communities; 845 | lnkS(l) = stability; 846 | end 847 | else 848 | for l=1:PARAMS.NbLouvain 849 | [stability, ~, communities] = stability_louvain_LNL(Graph, time, PARAMS.Precision, weighted); 850 | lnk(:,l) = communities; 851 | lnkS(l) = stability; 852 | end 853 | end 854 | [S,indexS]=max(lnkS); 855 | C=lnk(:,indexS); 856 | N=max(C)+1; 857 | 858 | clear communities; 859 | clear Graph; 860 | 861 | if PARAMS.ComputeVI && nnz(max(lnk)==PARAMS.NbNodes-1)~=PARAMS.NbLouvain... 862 | && nnz(max(lnk)==0)~=PARAMS.NbLouvain 863 | VI = computeRobustness(lnk,lnkS, PARAMS.M,PARAMS.ComputeParallel); 864 | else 865 | VI = 0; 866 | end 867 | 868 | clear lnk; 869 | 870 | end 871 | %------------------------------------------------------------------------------ 872 | function Graph = check(Graph, verbose, PARAMS) 873 | % Check that the graph is properly encoded. 874 | if verbose 875 | disp(' '); 876 | disp(' Graph sanity check...'); 877 | end 878 | 879 | % Initialisation of Graph properties 880 | edgelist = false; 881 | unweighted = false; 882 | 883 | if size(Graph,2) < 2 || size(Graph,1) < 3 884 | error(['The size of the graph is [' num2str(size(Graph,1)) ',' num2str(size(Graph,2)) ']. Please check that it has been correclty encoded.']) 885 | end 886 | 887 | if size(Graph,2) ~= size(Graph,1) 888 | if size(Graph,2) ~=2 && size(Graph,2) ~=3 889 | error('Wrong size for G: G should be a graph saved either as a list of edges (size(G)=[N,3] if weighted, size(G)=[N,2] if unweighted) or as an adjacency matrix (size(G)=[N,N])'); 890 | end 891 | edgelist = true; 892 | if size(Graph,2) == 2 893 | unweighted = true; 894 | end 895 | end 896 | 897 | % Check nodes numbering and convert edgelist into adjacency matrix 898 | if edgelist 899 | if min(min(Graph(:,1:2))) ~=0 900 | warning('The numbering of the nodes in the graph should always start with zero.'); 901 | old_node_1 = min(min(Graph(:,1:2))); 902 | Graph(:,1)=Graph(:,1)-old_node_1; 903 | Graph(:,2)=Graph(:,2)-old_node_1; 904 | end 905 | if unweighted == false 906 | Graph=sparse(Graph(:,1)+1,Graph(:,2)+1,Graph(:,3)); 907 | else 908 | Graph=sparse(Graph(:,1)+1,Graph(:,2)+1,ones(length(Graph(:,1)),1)); 909 | end 910 | end 911 | 912 | % Check that graph contains just numbers 913 | if any(any(~isnumeric(Graph))) 914 | error('The graph provided contains elements which are not numbers (isnumeric == false). Please check your graph, and try again.'); 915 | end 916 | 917 | % Check symmetry of the adjacency matrix if graph is not directed 918 | if PARAMS.directed == false 919 | if size(Graph,1) ~= size(Graph,2) 920 | error('The graph provided is a directed graph. Specify the correct options or correct your graph'); 921 | end 922 | if any(any(Graph~=Graph')) 923 | if nnz(triu(Graph,1))>0 && nnz(tril(Graph,-1))>0 924 | error('The graph provided is a directed graph.'); 925 | else 926 | warning('Adjacency matrix A of provided graph is triangular -- symmetrizing A = A + A^T'); 927 | Graph=Graph+Graph'; 928 | end 929 | end 930 | end 931 | 932 | % Check for isolated nodes 933 | if ( any( sum(abs(Graph))' == 0 & sum(abs(Graph),2) == 0 ) ) 934 | warning('There are isolated nodes in the graph!?'); 935 | end 936 | 937 | % Check for disconnected components 938 | if exist('graphconncomp','file') == 2 939 | nbcomp=graphconncomp(sparse(Graph),'WEAK',true); 940 | if nbcomp>1 941 | warning(['There are ' num2str(nbcomp) ' not strongly connected components in the graph. If your graph is directed please be aware of the teleportation settings.']); 942 | end 943 | end 944 | 945 | % Return Graph to its original form 946 | if edgelist 947 | [row, col, val] = find(Graph); 948 | if unweighted 949 | Graph=[col-1, row-1]; 950 | else 951 | Graph=[col-1, row-1, val]; 952 | end 953 | else 954 | Graph=sparse(Graph); 955 | end 956 | end 957 | %------------------------------------------------------------------------------ 958 | function VI = computeRobustness(lnk, lnkS, M,ComputeParallel) 959 | 960 | % Parameter 961 | 962 | [~,i] = sort(lnkS); 963 | lnk=lnk(:,i); 964 | lnk=lnk(:,end-M+1:end); 965 | VI = varinfo(lnk',ComputeParallel); 966 | clear i; 967 | end 968 | 969 | %------------------------------------------------------------------------------ 970 | function [] = stability_plot(Time,t,S,N,VI,ComputeVI,figure_handle) 971 | 972 | set(0,'CurrentFigure',figure_handle); 973 | 974 | if ComputeVI 975 | subplot(2,1,1), ax=plotyy(Time(1:t),N(1:t),Time(N>1),S(N>1)); 976 | else 977 | ax=plotyy(Time(1:t),N(1:t),Time(N>1),S(N>1)); 978 | end 979 | xlabel('Markov time'); 980 | set(ax(1),'YScale','log'); 981 | set(ax(2),'YScale','log'); 982 | set(ax(1),'YTickMode','auto','YTickLabelMode','auto','YMinorGrid','on'); 983 | set(ax(2),'YTickMode','auto','YTickLabelMode','auto','YMinorGrid','on'); 984 | set(get(ax(1),'Ylabel'),'String','Number of communities'); 985 | set(get(ax(2),'Ylabel'),'String','Stability'); 986 | set(ax(1),'XLim', [10^floor(log10(Time(1))) 10^ceil(log10(Time(end)))], 'YLim', [1 10^ceil(log10(max(N)))], 'XScale','log','XMinorGrid','on'); 987 | set(ax(2),'XLim', [10^floor(log10(Time(1))) 10^ceil(log10(Time(end)))], 'YLim', [10^floor(log10(min(S(N>1)))), 1], 'XScale','log'); 988 | ylabel('Number of communities'); 989 | if ComputeVI 990 | subplot(2,1,2), semilogx(Time(1:t),VI(1:t)); 991 | set(gca, 'XLim', [10^floor(log10(Time(1))) 10^ceil(log10(Time(end)))], 'YMinorGrid','on','XMinorGrid','on'); 992 | if max(VI)>0 993 | set(gca,'YLim', [0 max(VI)*1.1]); 994 | end 995 | xlabel('Markov time'); 996 | ylabel('Variation of information'); 997 | end 998 | drawnow; 999 | 1000 | end 1001 | -------------------------------------------------------------------------------- /bin/stability_v1.m: -------------------------------------------------------------------------------- 1 | function [S, N, VI, C] = stability_v1(G, T, varargin) 2 | %STABILITY Graph partitioning optimizing stability with the Louvain 3 | % algorithm 4 | % 5 | % [S, N, VI, C] = STABILITY(G, T) finds the optimal partitions of the 6 | % graph G by optimizing the stability at each Markov time in vector T. G 7 | % can either be the list of the edges in the graph (in the form [node i, 8 | % node j, weight of link i-j; node k, node l, weight of link k-l;...] if 9 | % the graph is weighted, or [node i, node j; node k, node l;...] if the 10 | % graph is unweighted) or the adjacendy matrix of the graph. S, N, VI and 11 | % C contain respectively the stability, the number of communities, the 12 | % variation of information, and the optimal partition for each 13 | % Markov time contained in T. If T is not specified, the modularity 14 | % (equivalent to stability for T=1) is calculated. Ideally, Markov time 15 | % should be sampled exponentially (e.g.: T = 10.^[-2:0.01:2]). 16 | % 17 | % [S, N, VI, C] = STABILITY(G, T,'PARAM',VALUE) accepts one or more 18 | % comma-separated parameter name/value pairs. For a list of parameters 19 | % and values, see "Parameter Options." 20 | % 21 | % 22 | % Parameter Options: 23 | % 24 | % Parameter Value Default 25 | % --------- ----- ------- 26 | % L Number of optimisations of the 100 27 | % Louvain algorithm to be done at 28 | % each Markov time. 29 | % 30 | % M The top M partitions among the L 100 31 | % given at each Markov time by the 32 | % L louvain optimisations will be 33 | % used to compute the variation of 34 | % information. 35 | % 36 | % laplacian Allows to choose which type of 'normalized' 37 | % laplacian should be used to 38 | % calculate the stability. It can 39 | % either be 'combinatorial', or 40 | % 'normalised'. 41 | % 42 | % noVI Disables the calculation of the none 43 | % robustness of the partitions. 44 | % Disabling this can significantly 45 | % speed up the calculations. 46 | % 47 | % out Enables saving step by step the '' 48 | % partitions found in a .mat file 49 | % located in the current folder, 50 | % along with the number of 51 | % communities (N), the value of 52 | % Stability (S), and the variation 53 | % of information (VI) for the 54 | % partitions obtained at each 55 | % Markov Time. 56 | % 57 | % full Enforces the calculation of the none 58 | % full stability. 59 | % 60 | % linearised Enforces the calculation of the none 61 | % linearised stability. 62 | % 63 | % nocheck Disables the checks for the none 64 | % encoding of the graph. This can 65 | % save computational time but can 66 | % also lead to serious errors if 67 | % the graph has not been properly 68 | % encoded. 69 | % 70 | % prec Precision: defines a threshold for 1e-9 71 | % the range of weights allowed in 72 | % the laplacian exponential matrix 73 | % of the full stability. 74 | % 75 | % plot Plots the plots the results of none 76 | % the stability, number of 77 | % communities and variation of 78 | % information as a function of the 79 | % Markov time. 80 | % 81 | % v Verbose mode. none 82 | % 83 | % p Parallel mode. none 84 | % 85 | % t Output as text files: none 86 | % Enables saving step by step the 87 | % partitions found at each Markov 88 | % time in a text file located in a 89 | % folder named 'Partitions', as 90 | % well as the outputs in a text 91 | % file output_'...'.stdout matlab 92 | % The option 'out' must be on. 93 | % 94 | 95 | 96 | 97 | %$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$% 98 | %$ $% 99 | %$ Default parameters $% 100 | %$ $% 101 | %$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$% 102 | 103 | Graph = []; % List of edges of the graph to be partitioned 104 | Time = 1; % Markov times at which the graph should be partitioned 105 | StabilityFunction = @louvain_LNL; % Linearised stability with normalised laplacian is used by default 106 | ComputeVI = true; % True if the variation of information should be computed 107 | OutputFile = false; % No output file by default. 108 | NbLouvain = 100; % Number of louvain optimisations at each Markov time 109 | NbNodes = 0; % Total number of nodes; 110 | Full = false; % If true, performs the full stability 111 | Sanity = true; % If true, performs the graph sanity checks 112 | Precision = 1e-10; % Threshold for stability and edges weigths 113 | plotStability = false; % If true, plots the results of the stability, number of communities and variation of information vs Markov time. 114 | verbose = false; % Toggles verbose mode 115 | weighted = 'u'; % 'u' for unweighted graph, 'w' for weighted graph 116 | prefix = ''; % Output prefix 117 | M = 100; % Top M partitions among the L found by louvain are used to compute the variation of information 118 | ComputeParallel = false; % Toggles the computation in parallel 119 | TextOutput = false; % Toggles the computation in parallel 120 | flag_matlabpool = false; 121 | 122 | 123 | %$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$% 124 | %$ $% 125 | %$ Arguments parsing $% 126 | %$ $% 127 | %$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$% 128 | 129 | % Options 130 | 131 | if nargin > 2 132 | [StabilityFunction, ComputeVI, OutputFile, prefix, NbLouvain, M, Full, Sanity, Precision, plotStability, verbose, ComputeParallel, TextOutput] = parseinput(length(varargin),G,varargin); 133 | end 134 | 135 | % Display starting time 136 | if verbose 137 | c = clock; 138 | disp(' '); 139 | disp([' Stability will be computed with the following parameters:']); 140 | disp(' '); 141 | if Full; disp(' - Full Stability'); else disp(' - Linearised Stability'); end 142 | if strfind(func2str(StabilityFunction), 'N'); disp([' - Normalised laplacian']); else disp([' - Combinatorial laplacian']); end 143 | if ComputeVI; disp([' - Computation of the variation of information (VI): Yes']); else disp([' - Computation of the variation of information: No']); end 144 | if OutputFile; disp([' - Save the results in a file: Yes ' prefix]); else disp([' - Save the results in a file: No']); end 145 | if OutputFile; disp([' - Saved file prefix: ' prefix]); end 146 | if Sanity; disp([' - Check the input graph: Yes']); else disp([' - Check the input graph: No']); end 147 | if plotStability; disp([' - Plot the results: Yes']); else disp([' - Plot the results: No']); end 148 | if verbose; disp([' - Verbose mode: Yes']); else disp([' - Verbose mode: No']); end 149 | if ComputeParallel; disp([' - Parallel computation: Yes']); else disp([' - Parallel computation: No']); end 150 | disp([' - Number of Louvain iterations: ' int2str(NbLouvain)]); 151 | disp([' - Number of Louvain iterations used for the computation of VI: ' int2str(M)]); 152 | disp([' - Precision used: ' num2str(Precision)]); 153 | disp(' '); 154 | tstart=tic; 155 | end 156 | 157 | 158 | % Argument 1: G 159 | 160 | if nargin > 0 161 | if size(G,1) == size(G,2) && ~issparse(G) 162 | G=sparse(G); 163 | end 164 | % Check if the graph is correctly encoded 165 | if Sanity 166 | G=check(G, verbose); 167 | end 168 | % If the full stability is to be computed, Graph should be the 169 | % adjacency matrix. 170 | if Full 171 | if size(G,1) ~= size(G,2) 172 | if size(G,2)==3 173 | Graph=sparse(G(:,1)+1,G(:,2)+1,G(:,3)); 174 | elseif size(G,2)==2 175 | Graph=sparse(G(:,1)+1,G(:,2)+1,ones(length(G(:,1)),1)); 176 | else 177 | error('Wrong size for G: G should be a graph saved either as a list of edges (size(G)=[N,3] if weighted, size(G)=[N,2] if unweighted) or as an adjacency matrix (size(G)=[N,N])'); 178 | end 179 | else 180 | Graph = sparse(G); 181 | end 182 | if max(max(Graph)) == 1 && isinteger(Graph) 183 | weighted='u'; 184 | else 185 | weighted='w'; 186 | end 187 | NbNodes = size(Graph,2); 188 | % if the linearised stability is to be computed, Graph should be the 189 | % list of edges. 190 | else 191 | if size(G,1) == size(G,2) 192 | [rows,cols,vals] = find(G); 193 | if sum(vals)==length(vals) 194 | Graph=[cols-1, rows-1]; 195 | weighted='u'; 196 | else 197 | Graph=[cols-1, rows-1, vals]; 198 | weighted='w'; 199 | end 200 | clear rows cols vals; 201 | elseif size(G,2)==2 202 | Graph=G; 203 | weighted='u'; 204 | elseif size(G,2)==3 205 | Graph=G; 206 | weighted='w'; 207 | else 208 | error('Wrong size for G: G should be a graph saved either as a list of edges (size(G)=[N,3] if weighted, size(G)=[N,2] if unweighted) or as an adjacency matrix (size(G)=[N,N])'); 209 | end 210 | NbNodes = max(Graph(:,1))+1; 211 | end 212 | else 213 | error('Please provide at least the graph to be partitioned. Type "help stability" for more information.'); 214 | end 215 | 216 | % Argument 2: T 217 | 218 | if nargin > 1 219 | if (isvector(T) && isnumeric(T)) 220 | Time=T; 221 | else 222 | error('The second argument should be a vector. Type "help stability" for more information.'); 223 | end 224 | end 225 | 226 | % Parallel computation: Initialize the number of cores if matlabpool is not 227 | % yet running. 228 | if ComputeParallel && matlabpool('size') == 0 229 | flag_matlabpool = true; 230 | matlabpool 231 | end 232 | 233 | %$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$% 234 | %$ $% 235 | %$ Computation of the stability $% 236 | %$ $% 237 | %$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$% 238 | 239 | % Display starting time 240 | if verbose 241 | c = clock; 242 | disp(' '); 243 | disp([' Partitioning of the graph started at ' datestr([2011 1 1 c(4) c(5) c(6)], 'HH:MM:SS')]); 244 | disp(' '); 245 | tstart=tic; 246 | end 247 | 248 | 249 | % Initialisation 250 | S = zeros(1, length(Time)); 251 | N = zeros(1, length(Time)); 252 | VI = zeros(1, length(Time)); 253 | C = zeros(NbNodes, length(Time)); 254 | 255 | if TextOutput 256 | mkdir(['Partitions_' prefix]); 257 | end 258 | 259 | if OutputFile 260 | save(['Stability_' prefix '.mat'],'Time','S','N','VI','C'); 261 | end 262 | 263 | if plotStability 264 | figure_handle = figure; 265 | end 266 | 267 | if verbose 268 | step_prec=0; 269 | end 270 | 271 | % Loop over all Markov times 272 | for t=1:length(Time) 273 | 274 | if verbose 275 | disp([' Partitioning for Markov time = ' num2str(Time(t),'%10.6f') '...']); 276 | end 277 | 278 | [S(t), N(t), C(:,t), VI(t)] = StabilityFunction(Graph, Time(t), Precision, weighted, ComputeVI, NbLouvain, M, NbNodes, ComputeParallel); 279 | 280 | if plotStability && t>1 281 | stability_plot(Time,t,S,N,VI,ComputeVI,figure_handle); 282 | end 283 | 284 | if TextOutput 285 | cd(['Partitions_' prefix]); 286 | dlmwrite(['Partition_' prefix '_' num2str(Time(t),'%10.6f') '.dat'],[[1:NbNodes]',C(:,t)],'delimiter','\t'); 287 | cd ..; 288 | dlmwrite(['Stability_' prefix '.stdout'],[Time(t), S(t), N(t), VI(t)],'-append', 'delimiter','\t') 289 | end 290 | 291 | if OutputFile 292 | save(['Stability_' prefix '.mat'],'Time','S','N','VI','C','-append'); 293 | end 294 | 295 | 296 | if verbose && 100*t/length(Time) >= step_prec+10 297 | disp(' '); 298 | disp([' Completed: ' num2str(round(100*t/length(Time)),10) '%']); 299 | remaining_time=toc(tstart)*(1-t/length(Time))/(t/length(Time)); 300 | nb_hours = floor(remaining_time/3600); 301 | nb_min = floor((remaining_time - nb_hours*3600)/60); 302 | nb_sec = round(remaining_time - nb_hours*3600 - nb_min*60); 303 | disp([' Estimated time remaining: ' datestr([2011 1 1 nb_hours nb_min nb_sec], 'HH:MM:SS')]);%num2str(nb_hours) ':' num2str(nb_min) ':' num2str(nb_sec)]); 304 | disp(' '); 305 | step_prec = floor(100*t/length(Time)); 306 | end 307 | 308 | end 309 | 310 | if verbose 311 | c = clock; 312 | disp(' '); 313 | disp([' Partitioning of the graph finished at ' datestr([2011 1 1 c(4) c(5) c(6)], 'HH:MM:SS')]); 314 | remaining_time=toc(tstart); 315 | nb_hours = floor(remaining_time/3600); 316 | nb_min = floor((remaining_time - nb_hours*3600)/60); 317 | nb_sec = round(remaining_time - nb_hours*3600 - nb_min*60); 318 | disp([' Total time needed: ' datestr([2011 1 1 nb_hours nb_min nb_sec], 'HH:MM:SS')]);%num2str(nb_hours) ':' num2str(nb_min) ':' num2str(nb_sec)]); 319 | end 320 | 321 | 322 | if OutputFile 323 | save(['Stability_' prefix '.mat'],'Time','S','N','VI','C','-append'); 324 | end 325 | 326 | if flag_matlabpool 327 | matlabpool close; 328 | end 329 | 330 | 331 | 332 | end 333 | 334 | %------------------------------------------------------------------------------ 335 | function [StabilityFunction, ComputeVI, OutputFile, prefix, NbLouvain, M, Full, Sanity, Precision, plotStability, verbose, ComputeParallel, TextOutput] = parseinput(options,G,varargin) 336 | % Parse the options 337 | 338 | % Initialise parameters 339 | 340 | % if (size(G,1) == size(G,2) && nnz(G)<5000 && size(G,1)<1000) || (size(G,1) ~= size(G,2) && size(G,1)<5000 && max(max(G(:,1:2)))<1000) 341 | % StabilityFunction = @louvain_FNL; % Linearised stability with normalised laplacian is used by default 342 | % else 343 | % StabilityFunction = @louvain_LNL; % Linearised stability with normalised laplacian is used by default 344 | % end 345 | 346 | stability_type_specified=false; 347 | threshold_nnodes_full = 1000; 348 | threshold_nedges_full = 5000; 349 | ComputeVI = true; % True if the variation of information should be computed 350 | OutputFile = false; % No output file by default. 351 | NbLouvain = 100; % Number of louvain optimisations at each Markov time 352 | Full = false; 353 | Laplacian = 'Normalised'; 354 | Sanity = true; 355 | Precision = 1e-10; 356 | plotStability = false; 357 | verbose = false; 358 | M = 100; 359 | prefix = ''; 360 | ComputeParallel = false; 361 | TextOutput = false; 362 | attributes={'novi', 'l', 'm', 'out', 'full', 'linearised', 'nocheck', 'laplacian', 'prec', 'plot','v','t','p'}; 363 | 364 | if options > 0 365 | 366 | varargin = varargin{:}; % extract cell array input from varargin 367 | 368 | % test whether attribute-value pairs are specified, or fixed parameter order 369 | stringoptions = lower(varargin(cellfun('isclass',varargin,'char'))); 370 | attributeindexesinoptionlist = ismember(stringoptions,attributes); 371 | newinputform = any(attributeindexesinoptionlist); 372 | if newinputform 373 | % parse values to functions parameters 374 | i = 1; 375 | while (i <= length(varargin)) 376 | if strcmpi(varargin{i},'full') 377 | stability_type_specified=true; 378 | Full = true; 379 | i = i+1; 380 | elseif strcmpi(varargin{i},'linearised') 381 | if stability_type_specified 382 | warning('The program can run either the linearised or the full stability, not both simultaneously. Please choose only one of them. Linearised stability will be used here.'); 383 | end 384 | stability_type_specified=true; 385 | Full = false; 386 | i = i+1; 387 | elseif strcmpi(varargin{i},'novi') 388 | ComputeVI = false; 389 | i = i+1; 390 | elseif strcmpi(varargin{i},'nocheck') 391 | Sanity = false; 392 | i = i+1; 393 | elseif strcmpi(varargin{i},'plot') 394 | plotStability = true; 395 | i = i+1; 396 | elseif strcmpi(varargin{i},'v') 397 | verbose = true; 398 | i = i+1; 399 | elseif strcmpi(varargin{i},'p') 400 | if exist('matlabpool','file') 401 | ComputeParallel = true; 402 | else 403 | ComputeParallel = false; 404 | warning('The Parallel Computing Toolbox of Matlab does not appear to be installed. Defaulting to single node computation...'); 405 | end 406 | i = i+1; 407 | elseif strcmpi(varargin{i},'t') 408 | TextOutput = true; 409 | i = i+1; 410 | else 411 | %Check to make sure that there is a pair to go with 412 | %this argument. 413 | if length(varargin) < i + 1 414 | error('MATLAB:stability:AttributeList', ... 415 | 'Attribute %s requires a matching value', varargin{i}); 416 | elseif strcmpi(varargin{i},'laplacian') 417 | if ischar(varargin{i+1}) 418 | Laplacian = varargin{i+1}; 419 | else 420 | error('MATLAB:stability:laplacian',... 421 | 'Please provide a matching value for attribute laplacian. It must either be ''normalised'' or ''combinatorial''.'); 422 | end 423 | elseif strcmpi(varargin{i},'l') 424 | if isnumeric(varargin{i+1}) 425 | NbLouvain = round(varargin{i+1}); 426 | M = round(varargin{i+1}); 427 | end 428 | elseif strcmpi(varargin{i},'prec') 429 | if isnumeric(varargin{i+1}) 430 | Precision = varargin{i+1}; 431 | end 432 | elseif strcmpi(varargin{i},'m') 433 | if isnumeric(varargin{i+1}) 434 | M = varargin{i+1}; 435 | end 436 | elseif strcmpi(varargin{i},'out') 437 | if ischar(varargin{i+1}) 438 | OutputFile = true; 439 | prefix = varargin{i+1}; 440 | else 441 | error('MATLAB:stability:out',... 442 | 'Please provide a matching value for attribute out. It must be a string.'); 443 | end 444 | else 445 | error('MATLAB:stability:Attribute',... 446 | 'Invalid attribute tag: %s', varargin{i}); 447 | end 448 | i = i+2; 449 | end 450 | end 451 | else 452 | if ischar(varargin{1}) 453 | error('MATLAB:stability:Attribute',... 454 | 'Invalid attribute tag: %s', varargin{1}); 455 | else 456 | error('MATLAB:stability:Attribute',... 457 | 'Invalid attribute tag: %d', varargin{1}); 458 | end 459 | end 460 | end 461 | 462 | TextOutput = TextOutput & OutputFile; 463 | 464 | if ~stability_type_specified 465 | if (size(G,1) == size(G,2) && nnz(G)0 && nnz(tril(Graph,-1))>0 745 | error('The graph provided is a directed graph. This program only deals with undirected graphs.'); 746 | else 747 | warning('Adjacency matrix A of provided graph is triangular -- symmetrizing A = A + A^T'); 748 | Graph=Graph+Graph'; 749 | end 750 | end 751 | 752 | % Check for isolated nodes 753 | if nnz(sum(Graph))~=size(Graph,2) 754 | warning('There are isolated nodes in the graph'); 755 | end 756 | 757 | % Check for disconnected components 758 | if exist('graphconncomp','file') == 2 759 | nbcomp=graphconncomp(sparse(Graph)); 760 | if nbcomp>1 761 | warning(['There are ' num2str(nbcomp) ' disconnected components in the graph.']); 762 | end 763 | end 764 | 765 | % Return Graph to its original form 766 | if edgelist 767 | [row, col, val] = find(Graph); 768 | if unweighted 769 | Graph=[col-1, row-1]; 770 | else 771 | Graph=[col-1, row-1, val]; 772 | end 773 | else 774 | Graph=sparse(Graph); 775 | end 776 | end 777 | %------------------------------------------------------------------------------ 778 | function VI = computeRobustness(lnk, lnkS, M, ComputeParallel) 779 | 780 | [i,i] = sort(lnkS); 781 | lnk=lnk(:,i); 782 | lnk=lnk(:,end-M+1:end); 783 | VI = varinfo(lnk',ComputeParallel); 784 | clear i; 785 | 786 | end 787 | 788 | %------------------------------------------------------------------------------ 789 | function [] = stability_plot(Time,t,S,N,VI,ComputeVI,figure_handle) 790 | 791 | set(0,'CurrentFigure',figure_handle); 792 | 793 | if ComputeVI 794 | subplot(2,1,1), ax=plotyy(Time(1:t),N(1:t),Time(N>1),S(N>1)); 795 | else 796 | ax=plotyy(Time(1:t),N(1:t),Time(N>1),S(N>1)); 797 | end 798 | xlabel('Markov time'); 799 | set(ax(1),'YScale','log'); 800 | set(ax(2),'YScale','log'); 801 | set(ax(1),'YTickMode','auto','YTickLabelMode','auto','YMinorGrid','on'); 802 | set(ax(2),'YTickMode','auto','YTickLabelMode','auto','YMinorGrid','on'); 803 | set(get(ax(1),'Ylabel'),'String','Number of communities'); 804 | set(get(ax(2),'Ylabel'),'String','Stability'); 805 | set(ax(1),'XLim', [10^floor(log10(Time(1))) 10^ceil(log10(Time(end)))], 'YLim', [1 10^ceil(log10(max(N)))], 'XScale','log','XMinorGrid','on'); 806 | set(ax(2),'XLim', [10^floor(log10(Time(1))) 10^ceil(log10(Time(end)))], 'YLim', [10^floor(log10(min(S(N>1)))), 1], 'XScale','log'); 807 | ylabel('Number of communities'); 808 | if ComputeVI 809 | subplot(2,1,2), semilogx(Time(1:t),VI(1:t)); 810 | set(gca, 'XLim', [10^floor(log10(Time(1))) 10^ceil(log10(Time(end)))], 'YMinorGrid','on','XMinorGrid','on'); 811 | if max(VI)>0 812 | set(gca,'YLim', [0 max(VI)*1.1]); 813 | end 814 | xlabel('Markov time'); 815 | ylabel('Variation of information'); 816 | end 817 | drawnow; 818 | 819 | end 820 | -------------------------------------------------------------------------------- /bin/varinfo.m: -------------------------------------------------------------------------------- 1 | function [vi,vi_mat] = varinfo(partition_vectors,ComputeParallel) 2 | %VARINFO Calculates the variation of information matrix and average 3 | % between all pairs of a set of partitions 4 | % 5 | % [VI,VI_MAT] = VARINFO(P) calculates the variation of information between 6 | % each pair of partitions contained in P, where P is the N by M matrix 7 | % of partitions where N is the number of nodes in the original graph 8 | % and M is the number of partitions. The output VI is the average variation 9 | % of information between all pairs of partitions, and VI_MAT is the M by M 10 | % matrix where entry (i,j) is the variation of information between 11 | % the partitions contained in column i and j of the matrix P. 12 | % 13 | % [VI,VI_MAT] = VARINFO(P,F) allows the calculation of the variation of 14 | % information in parallel if the boolean F is true and provided that 15 | % matlab pool is running. 16 | % 17 | % This code has been adapted from the code originally implemented for the 18 | % following paper: 19 | % 20 | % The performance of modularity maximization in practical contexts. 21 | % B. H. Good, Y.-A. de Montjoye and A. Clauset. 22 | % Physical Review E 81, 046106 (2010). 23 | % 24 | % The original code can be found at: 25 | % http://tuvalu.santafe.edu/~aaronc/modularity/ 26 | 27 | number_of_partitions = size(partition_vectors,1); 28 | n = size(partition_vectors,2); 29 | vi_mat = zeros(number_of_partitions); 30 | vi=0; 31 | 32 | % If all the partitions are identical, vi=0 and there is no need to do the 33 | % rest of the calculations which are computationally expensive. 34 | if all(all(partition_vectors==repmat(partition_vectors(1,:),number_of_partitions,1))) 35 | return; 36 | end 37 | 38 | % Select only the partitions which are different 39 | [partition_vectors,b,c] = unique(partition_vectors,'rows'); 40 | 41 | number_of_partitions=length(b); 42 | 43 | vi_mat = zeros(number_of_partitions); 44 | 45 | vi_tot=0; 46 | nodes = 1:n; 47 | 48 | if nargin==2 && ComputeParallel 49 | parfor i = 1:number_of_partitions 50 | partition_1 = partition_vectors(i,:); 51 | partition_1 = double(partition_1)+1; 52 | A_1 = sparse(partition_1,nodes,1); 53 | n_1_all = sum(A_1,2); 54 | vi_mat_row=vi_mat(i,:); 55 | 56 | for j = 1:i-1 57 | partition_2 = partition_vectors(j,:); 58 | partition_2 = double(partition_2)+1; 59 | A_2 = sparse(nodes,partition_2,1); 60 | n_2_all = sum(A_2,1)'; 61 | n_12_all = A_1*A_2; 62 | 63 | [rows,cols,n_12] = find(n_12_all); 64 | 65 | n_1 = n_1_all(rows); 66 | n_2 = n_2_all(cols); 67 | 68 | vi = sum(n_12.*log(n_12.^2./(n_1.*n_2))); 69 | vi = -1/(n*log(n))*vi; 70 | 71 | vi_mat_row(j)=vi; 72 | 73 | vi_tot=vi_tot+vi; 74 | 75 | end 76 | vi_mat(i,:)=vi_mat_row; 77 | end 78 | else 79 | for i = 1:number_of_partitions 80 | partition_1 = partition_vectors(i,:); 81 | partition_1 = double(partition_1)+1; 82 | A_1 = sparse(partition_1,nodes,1); 83 | n_1_all = sum(A_1,2); 84 | 85 | for j = 1:i-1 86 | partition_2 = partition_vectors(j,:); 87 | partition_2 = double(partition_2)+1; 88 | A_2 = sparse(nodes,partition_2,1); 89 | n_2_all = sum(A_2,1)'; 90 | n_12_all = A_1*A_2; 91 | 92 | [rows,cols,n_12] = find(n_12_all); 93 | 94 | n_1 = n_1_all(rows); 95 | n_2 = n_2_all(cols); 96 | 97 | vi = sum(n_12.*log(n_12.^2./(n_1.*n_2))); 98 | vi = -1/(n*log(n))*vi; 99 | vi_mat(i,j)=vi; 100 | vi_tot=vi_tot+vi; 101 | 102 | end 103 | end 104 | end 105 | 106 | vi_mat_full = zeros(number_of_partitions,length(c)); 107 | 108 | for i=1:number_of_partitions 109 | vi_mat_full(i,:) = vi_mat(i,c); 110 | end 111 | vi_mat_full=vi_mat_full(c,:); 112 | 113 | vi_mat = vi_mat_full+vi_mat_full'; 114 | 115 | vi = mean(squareform(vi_mat)); 116 | 117 | end 118 | -------------------------------------------------------------------------------- /demo/Protein_AdK.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelschaub/PartitionStability/8e2b9ee6d62c7e60c92dbe8cd0f2532e87c86b09/demo/Protein_AdK.mat -------------------------------------------------------------------------------- /demo/demo.fig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelschaub/PartitionStability/8e2b9ee6d62c7e60c92dbe8cd0f2532e87c86b09/demo/demo.fig -------------------------------------------------------------------------------- /demo/demo.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelschaub/PartitionStability/8e2b9ee6d62c7e60c92dbe8cd0f2532e87c86b09/demo/demo.mat -------------------------------------------------------------------------------- /demo/demo.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelschaub/PartitionStability/8e2b9ee6d62c7e60c92dbe8cd0f2532e87c86b09/demo/demo.pdf -------------------------------------------------------------------------------- /demo/ring_of_rings.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelschaub/PartitionStability/8e2b9ee6d62c7e60c92dbe8cd0f2532e87c86b09/demo/ring_of_rings.mat --------------------------------------------------------------------------------